diff --git a/bin/scripts/unix/idea.sh b/bin/scripts/unix/idea.sh
index a22209fa94ce..ec5b90e3f380 100755
--- a/bin/scripts/unix/idea.sh
+++ b/bin/scripts/unix/idea.sh
@@ -63,7 +63,6 @@ fi
# ---------------------------------------------------------------------
if [ -n "$@@product_uc@@_JDK" -a -x "$@@product_uc@@_JDK/bin/java" ]; then
JDK="$@@product_uc@@_JDK"
- echo "@@product_uc@@_JDK: $@@product_uc@@_JDK"
fi
if [ -z "$JDK" ] || [ ! -x "$JDK/bin/java" ] &&
@@ -72,25 +71,21 @@ if [ -z "$JDK" ] || [ ! -x "$JDK/bin/java" ] &&
if [ ! -d "$JDK" ]; then
JDK="$IDE_HOME/$JDK"
fi
- echo "boot jdk: $JDK"
fi
if [ -z "$JDK" ] || [ ! -x "$JDK/bin/java" ] &&
[ "$OS_TYPE" = "Linux" ] && [ -x "$IDE_HOME/jre64/bin/java" ] && "$IDE_HOME/jre64/bin/java" -version > /dev/null 2>&1 ; then
JDK="$IDE_HOME/jre64"
- echo "bundled jre: $JDK"
fi
if [ -z "$JDK" ] || [ ! -x "$JDK/bin/java" ] &&
[ -n "$JDK_HOME" -a -x "$JDK_HOME/bin/java" ]; then
JDK="$JDK_HOME"
- echo "JDK_HOME: $JDK"
fi
if [ -z "$JDK" ] || [ ! -x "$JDK/bin/java" ]; then
if [ -n "$JAVA_HOME" -a -x "$JAVA_HOME/bin/java" ]; then
JDK="$JAVA_HOME"
- echo "JAVA_HOME: $JDK"
else
JAVA_BIN_PATH=`which java`
if [ -n "$JAVA_BIN_PATH" ]; then
diff --git a/build.xml b/build.xml
index 7f432c016be6..33716395f4f7 100644
--- a/build.xml
+++ b/build.xml
@@ -75,7 +75,7 @@
-
+
diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsImpl.java b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsImpl.java
index 1812ade79721..c0f6c45d86d3 100644
--- a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsImpl.java
+++ b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsImpl.java
@@ -226,20 +226,16 @@ public class DebuggerUtilsImpl extends DebuggerUtilsEx{
return Boolean.TRUE.equals(debugProcess.getUserData(BatchEvaluator.REMOTE_SESSION_KEY));
}
- public interface SupplierThrowing {
- T get() throws E;
- }
-
- public static T suppressExceptions(SupplierThrowing supplier, T defaultValue) throws E {
+ public static T suppressExceptions(ThrowableComputable supplier, T defaultValue) throws E {
return suppressExceptions(supplier, defaultValue, true, null);
}
- public static T suppressExceptions(SupplierThrowing supplier,
+ public static T suppressExceptions(ThrowableComputable supplier,
T defaultValue,
boolean ignorePCE,
Class rethrow) throws E {
try {
- return supplier.get();
+ return supplier.compute();
}
catch (ProcessCanceledException e) {
if (!ignorePCE) {
diff --git a/java/debugger/impl/src/com/intellij/debugger/jdi/VirtualMachineProxyImpl.java b/java/debugger/impl/src/com/intellij/debugger/jdi/VirtualMachineProxyImpl.java
index cb67d2b23a8d..55ebaef45f6a 100644
--- a/java/debugger/impl/src/com/intellij/debugger/jdi/VirtualMachineProxyImpl.java
+++ b/java/debugger/impl/src/com/intellij/debugger/jdi/VirtualMachineProxyImpl.java
@@ -25,8 +25,8 @@ import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.DebuggerManagerThreadImpl;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.jdi.VirtualMachineProxy;
-import com.intellij.debugger.impl.DebuggerUtilsImpl;
import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.ThreeState;
@@ -357,13 +357,13 @@ public class VirtualMachineProxyImpl implements JdiTimer, VirtualMachineProxy {
return myVirtualMachine.mirrorOf(s);
}
- public StringReference mirrorOfStringLiteral(String s, DebuggerUtilsImpl.SupplierThrowing generator)
+ public StringReference mirrorOfStringLiteral(String s, ThrowableComputable generator)
throws EvaluateException {
StringReference reference = myStringLiteralCache.get(s);
if (reference != null && !reference.isCollected()) {
return reference;
}
- reference = generator.get();
+ reference = generator.compute();
myStringLiteralCache.put(s, reference);
return reference;
}
diff --git a/java/execution/impl/src/com/intellij/execution/junit/JUnitUtil.java b/java/execution/impl/src/com/intellij/execution/junit/JUnitUtil.java
index fe6612b10d08..2fa79311b8c6 100644
--- a/java/execution/impl/src/com/intellij/execution/junit/JUnitUtil.java
+++ b/java/execution/impl/src/com/intellij/execution/junit/JUnitUtil.java
@@ -259,7 +259,7 @@ public class JUnitUtil {
return aPackage != null && aPackage.getDirectories(scope).length > 0;
};
- return foundCondition.value(TEST5_PACKAGE_FQN) || foundCondition.value("org.junit.platform.engine");
+ return foundCondition.value(TEST5_PACKAGE_FQN);
}
public static boolean isTestAnnotated(final PsiMethod method) {
diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/MarkerType.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/MarkerType.java
index 580244b0b882..7da422c36c5a 100644
--- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/MarkerType.java
+++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/MarkerType.java
@@ -237,7 +237,7 @@ public class MarkerType {
PsiMethod[] overridings = processor.toArray(PsiMethod.EMPTY_ARRAY);
if (overridings.length == 0) {
final PsiClass aClass = method.getContainingClass();
- if (aClass != null && FunctionalExpressionSearch.search(aClass).findFirst() != null) {
+ if (aClass != null && isAbstract && FunctionalExpressionSearch.search(aClass).findFirst() != null) {
return "Has functional implementations";
}
return null;
@@ -462,17 +462,19 @@ public class MarkerType {
return super.process(psiMethod);
}
});
- PsiClass psiClass = ReadAction.compute(myMethod::getContainingClass);
- FunctionalExpressionSearch.search(psiClass).forEach(new CommonProcessors.CollectProcessor() {
- @Override
- public boolean process(final PsiFunctionalExpression expr) {
- if (!updateComponent(expr, myRenderer.getComparator())) {
- indicator.cancel();
+ if (myMethod.hasModifierProperty(PsiModifier.ABSTRACT)) {
+ PsiClass psiClass = ReadAction.compute(myMethod::getContainingClass);
+ FunctionalExpressionSearch.search(psiClass).forEach(new CommonProcessors.CollectProcessor() {
+ @Override
+ public boolean process(final PsiFunctionalExpression expr) {
+ if (!updateComponent(expr, myRenderer.getComparator())) {
+ indicator.cancel();
+ }
+ indicator.checkCanceled();
+ return super.process(expr);
}
- indicator.checkCanceled();
- return super.process(expr);
- }
- });
+ });
+ }
}
}
}
diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/smartEnter/MissingIfBranchesFixer.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/smartEnter/MissingIfBranchesFixer.java
index 96172132642a..89011af15f93 100644
--- a/java/java-impl/src/com/intellij/codeInsight/editorActions/smartEnter/MissingIfBranchesFixer.java
+++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/smartEnter/MissingIfBranchesFixer.java
@@ -43,7 +43,7 @@ public class MissingIfBranchesFixer implements Fixer {
}
private static void handleBranch(@NotNull Document doc, @NotNull PsiIfStatement ifStatement, @NotNull PsiElement beforeBranch, @Nullable PsiStatement branch) {
- if (branch instanceof PsiBlockStatement) return;
+ if (branch instanceof PsiBlockStatement || beforeBranch.textMatches(PsiKeyword.ELSE) && branch instanceof PsiIfStatement) return;
boolean transformingOneLiner = branch != null && (startLine(doc, beforeBranch) == startLine(doc, branch) ||
startCol(doc, ifStatement) < startCol(doc, branch));
diff --git a/java/java-tests/testData/codeInsight/completeStatement/BlockBeforeElseIf.java b/java/java-tests/testData/codeInsight/completeStatement/BlockBeforeElseIf.java
new file mode 100644
index 000000000000..cbdd02aa8689
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/completeStatement/BlockBeforeElseIf.java
@@ -0,0 +1,7 @@
+class Foo {
+ {
+ if (a)
+ else if (elsecond) {
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/completeStatement/BlockBeforeElseIf_after.java b/java/java-tests/testData/codeInsight/completeStatement/BlockBeforeElseIf_after.java
new file mode 100644
index 000000000000..ad424e86ea47
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/completeStatement/BlockBeforeElseIf_after.java
@@ -0,0 +1,8 @@
+class Foo {
+ {
+ if (a) {
+
+ } else if (elsecond) {
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/CompleteStatementTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/CompleteStatementTest.java
index 75557d713163..24fddf9288ac 100644
--- a/java/java-tests/testSrc/com/intellij/codeInsight/CompleteStatementTest.java
+++ b/java/java-tests/testSrc/com/intellij/codeInsight/CompleteStatementTest.java
@@ -123,6 +123,8 @@ public class CompleteStatementTest extends EditorActionTestCase {
public void testElseIf() throws Exception { doTest(); }
+ public void testBlockBeforeElseIf() { doTest(); }
+
public void testIncompleteElseIf() throws Exception { doTest(); }
public void testField() throws Exception { doTest(); }
diff --git a/java/testFramework/src/com/intellij/codeInsight/daemon/quickFix/ActionHint.java b/java/testFramework/src/com/intellij/codeInsight/daemon/quickFix/ActionHint.java
index 897d79453bd4..15d0e5636a05 100644
--- a/java/testFramework/src/com/intellij/codeInsight/daemon/quickFix/ActionHint.java
+++ b/java/testFramework/src/com/intellij/codeInsight/daemon/quickFix/ActionHint.java
@@ -40,9 +40,9 @@ import static org.junit.Assert.fail;
* @author Tagir Valeev
*/
public class ActionHint {
- final String myExpectedText;
- final boolean myShouldPresent;
- final ProblemHighlightType myHighlightType;
+ private final String myExpectedText;
+ private final boolean myShouldPresent;
+ private final ProblemHighlightType myHighlightType;
private ActionHint(String expectedText, boolean shouldPresent, ProblemHighlightType severity) {
myExpectedText = expectedText;
@@ -67,7 +67,7 @@ public class ActionHint {
* @return true if this ActionHint checks that some action should be present
* or false if it checks that some action should be absent
*/
- public boolean shouldPresent() {
+ boolean shouldPresent() {
return myShouldPresent;
}
diff --git a/platform/analysis-api/src/com/intellij/codeInsight/intention/IntentionActionBean.java b/platform/analysis-api/src/com/intellij/codeInsight/intention/IntentionActionBean.java
index 5f08b7cd1c62..9d5a4f9c533a 100644
--- a/platform/analysis-api/src/com/intellij/codeInsight/intention/IntentionActionBean.java
+++ b/platform/analysis-api/src/com/intellij/codeInsight/intention/IntentionActionBean.java
@@ -22,12 +22,11 @@ import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.CustomLoadingExtensionPointBean;
-import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.xmlb.annotations.Tag;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.util.Locale;
import java.util.ResourceBundle;
public class IntentionActionBean extends CustomLoadingExtensionPointBean {
@@ -67,6 +66,7 @@ public class IntentionActionBean extends CustomLoadingExtensionPointBean {
return descriptionDirectoryName;
}
+ @NotNull
public IntentionAction instantiate() throws ClassNotFoundException {
return (IntentionAction)instantiateExtension(className, ApplicationManager.getApplication().getPicoContainer());
}
diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/CompilationTasksImpl.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/CompilationTasksImpl.groovy
index 81050a164483..8995de6c7476 100644
--- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/CompilationTasksImpl.groovy
+++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/CompilationTasksImpl.groovy
@@ -42,7 +42,6 @@ class CompilationTasksImpl extends CompilationTasks {
ensureKotlinCompilerAddedToClassPath()
- context.projectBuilder.cleanOutput()
context.messages.progress("Compiling project")
try {
if (moduleNames == null) {
diff --git a/platform/core-api/src/com/intellij/psi/search/DelegatingGlobalSearchScope.java b/platform/core-api/src/com/intellij/psi/search/DelegatingGlobalSearchScope.java
index 7f7f05512b9c..1d2a61961d79 100644
--- a/platform/core-api/src/com/intellij/psi/search/DelegatingGlobalSearchScope.java
+++ b/platform/core-api/src/com/intellij/psi/search/DelegatingGlobalSearchScope.java
@@ -75,6 +75,11 @@ public class DelegatingGlobalSearchScope extends GlobalSearchScope {
return myBaseScope.getDisplayName();
}
+ @Override
+ public String toString() {
+ return getClass().getName() + "[" + myBaseScope + "]";
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/platform/core-impl/src/com/intellij/extapi/psi/StubBasedPsiElementBase.java b/platform/core-impl/src/com/intellij/extapi/psi/StubBasedPsiElementBase.java
index 0b2bb26ca9db..06f5f6092073 100644
--- a/platform/core-impl/src/com/intellij/extapi/psi/StubBasedPsiElementBase.java
+++ b/platform/core-impl/src/com/intellij/extapi/psi/StubBasedPsiElementBase.java
@@ -24,7 +24,6 @@ import com.intellij.openapi.diagnostic.RuntimeExceptionWithAttachments;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectCoreUtil;
-import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.RecursionManager;
import com.intellij.openapi.vfs.VirtualFile;
@@ -367,7 +366,7 @@ public class StubBasedPsiElementBase extends ASTDelegateP
@NotNull
public IStubElementType getElementType() {
if (!(myElementType instanceof IStubElementType)) {
- throw new AssertionError("Not a stub type: " + myElementType + " in " + getClass());
+ throw new ClassCastException("Not a stub type: " + myElementType + " in " + getClass());
}
return (IStubElementType)myElementType;
}
diff --git a/platform/core-impl/src/com/intellij/util/CachedValueLeakChecker.java b/platform/core-impl/src/com/intellij/util/CachedValueLeakChecker.java
index 71f7447ae1e3..0e8ddb7c5d44 100644
--- a/platform/core-impl/src/com/intellij/util/CachedValueLeakChecker.java
+++ b/platform/core-impl/src/com/intellij/util/CachedValueLeakChecker.java
@@ -23,6 +23,7 @@ import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.CachedValueProvider;
@@ -42,6 +43,7 @@ class CachedValueLeakChecker {
private static final Logger LOG = Logger.getInstance("#com.intellij.util.CachedValueChecker");
private static final boolean DO_CHECKS = ApplicationManager.getApplication().isUnitTestMode();
private static final Set ourCheckedKeys = ContainerUtil.newConcurrentSet();
+ private static final boolean JAVA9 = SystemInfo.isJavaVersionAtLeast("9");
static void checkProvider(@NotNull final CachedValueProvider provider,
@NotNull final Key key,
@@ -49,7 +51,9 @@ class CachedValueLeakChecker {
if (!DO_CHECKS || ApplicationInfoImpl.isInStressTest()) return;
if (!ourCheckedKeys.add(key.toString())) return; // store strings because keys are created afresh in each (test) project
- findReferencedPsi(provider, userDataHolder, 5);
+ if (!JAVA9) {
+ findReferencedPsi(provider, userDataHolder, 5);
+ }
}
private static synchronized void findReferencedPsi(@NotNull final Object root,
diff --git a/platform/core-impl/src/com/intellij/util/CachedValuesManagerImpl.java b/platform/core-impl/src/com/intellij/util/CachedValuesManagerImpl.java
index 28c2a6cd69e7..a3b77c6ffe93 100644
--- a/platform/core-impl/src/com/intellij/util/CachedValuesManagerImpl.java
+++ b/platform/core-impl/src/com/intellij/util/CachedValuesManagerImpl.java
@@ -15,10 +15,10 @@
*/
package com.intellij.util;
-import com.intellij.openapi.util.UserDataHolder;
-import com.intellij.openapi.util.Key;
-import com.intellij.openapi.util.UserDataHolderEx;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.UserDataHolder;
+import com.intellij.openapi.util.UserDataHolderEx;
import com.intellij.psi.util.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
diff --git a/platform/diff-impl/src/com/intellij/diff/merge/TextMergeViewer.java b/platform/diff-impl/src/com/intellij/diff/merge/TextMergeViewer.java
index a6ebdc48cc8a..7d1274659e29 100644
--- a/platform/diff-impl/src/com/intellij/diff/merge/TextMergeViewer.java
+++ b/platform/diff-impl/src/com/intellij/diff/merge/TextMergeViewer.java
@@ -36,6 +36,7 @@ import com.intellij.diff.tools.util.base.HighlightPolicy;
import com.intellij.diff.tools.util.base.IgnorePolicy;
import com.intellij.diff.tools.util.base.TextDiffViewerUtil;
import com.intellij.diff.tools.util.text.LineOffsets;
+import com.intellij.diff.tools.util.text.LineOffsetsUtil;
import com.intellij.diff.tools.util.text.MergeInnerDifferences;
import com.intellij.diff.tools.util.text.TextDiffProviderBase;
import com.intellij.diff.util.*;
@@ -406,7 +407,7 @@ public class TextMergeViewer implements MergeTool.MergeViewer {
indicator.checkCanceled();
return ContainerUtil.map(contents, content -> content.getDocument().getImmutableCharSequence());
});
- List lineOffsets = ContainerUtil.map(sequences, LineOffsets::create);
+ List lineOffsets = ContainerUtil.map(sequences, LineOffsetsUtil::create);
ComparisonManager manager = ComparisonManager.getInstance();
List lineFragments = manager.compareLines(sequences.get(0), sequences.get(1), sequences.get(2),
diff --git a/platform/diff-impl/src/com/intellij/diff/tools/util/text/LineOffsets.java b/platform/diff-impl/src/com/intellij/diff/tools/util/text/LineOffsets.java
index 7b952afc6f98..3c72d8fe255b 100644
--- a/platform/diff-impl/src/com/intellij/diff/tools/util/text/LineOffsets.java
+++ b/platform/diff-impl/src/com/intellij/diff/tools/util/text/LineOffsets.java
@@ -15,61 +15,14 @@
*/
package com.intellij.diff.tools.util.text;
-import com.intellij.openapi.util.text.StringUtil;
-import gnu.trove.TIntArrayList;
-import org.jetbrains.annotations.NotNull;
+public interface LineOffsets {
+ int getLineStart(int line);
-public class LineOffsets {
- private final int[] myLineEnds;
- private final int myTextLength;
+ int getLineEnd(int line);
- private LineOffsets(int[] ends, int length) {
- myLineEnds = ends;
- myTextLength = length;
- }
+ int getLineNumber(int offset);
- public int getLineStart(int line) {
- checkLineIndex(line);
- if (line == 0) return 0;
- return myLineEnds[line - 1] + 1;
- }
+ int getLineCount();
- public int getLineEnd(int line) {
- checkLineIndex(line);
- return myLineEnds[line];
- }
-
- public int getLineCount() {
- return myLineEnds.length;
- }
-
- public int getTextLength() {
- return myTextLength;
- }
-
- private void checkLineIndex(int index) {
- if (index < 0 || index >= getLineCount()) {
- throw new IndexOutOfBoundsException("Wrong line: " + index + ". Available lines count: " + getLineCount());
- }
- }
-
- @NotNull
- public static LineOffsets create(@NotNull CharSequence text) {
- TIntArrayList ends = new TIntArrayList();
-
- int index = 0;
- while (true) {
- int lineEnd = StringUtil.indexOf(text, '\n', index);
- if (lineEnd != -1) {
- ends.add(lineEnd);
- index = lineEnd + 1;
- }
- else {
- ends.add(text.length());
- break;
- }
- }
-
- return new LineOffsets(ends.toNativeArray(), text.length());
- }
+ int getTextLength();
}
diff --git a/platform/diff-impl/src/com/intellij/diff/tools/util/text/LineOffsetsUtil.java b/platform/diff-impl/src/com/intellij/diff/tools/util/text/LineOffsetsUtil.java
new file mode 100644
index 000000000000..273cce8b5aab
--- /dev/null
+++ b/platform/diff-impl/src/com/intellij/diff/tools/util/text/LineOffsetsUtil.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.diff.tools.util.text;
+
+import com.intellij.diff.util.DiffUtil;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.util.text.StringUtil;
+import gnu.trove.TIntArrayList;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+
+public class LineOffsetsUtil {
+ public static LineOffsets create(@NotNull Document document) {
+ return new LineOffsetsDocumentWrapper(document);
+ }
+
+ @NotNull
+ public static LineOffsets create(@NotNull CharSequence text) {
+ TIntArrayList ends = new TIntArrayList();
+
+ int index = 0;
+ while (true) {
+ int lineEnd = StringUtil.indexOf(text, '\n', index);
+ if (lineEnd != -1) {
+ ends.add(lineEnd);
+ index = lineEnd + 1;
+ }
+ else {
+ ends.add(text.length());
+ break;
+ }
+ }
+
+ return new LineOffsetsImpl(ends.toNativeArray(), text.length());
+ }
+
+ private static class LineOffsetsImpl implements LineOffsets {
+ private final int[] myLineEnds;
+ private final int myTextLength;
+
+ private LineOffsetsImpl(int[] lineEnds, int textLength) {
+ myLineEnds = lineEnds;
+ myTextLength = textLength;
+ }
+
+ public int getLineStart(int line) {
+ checkLineIndex(line);
+ if (line == 0) return 0;
+ return myLineEnds[line - 1] + 1;
+ }
+
+ public int getLineEnd(int line) {
+ checkLineIndex(line);
+ return myLineEnds[line];
+ }
+
+ @Override
+ public int getLineNumber(int offset) {
+ if (offset < 0 || offset > getTextLength()) {
+ throw new IndexOutOfBoundsException("Wrong offset: " + offset + ". Available text length: " + getTextLength());
+ }
+ if (offset == 0) return 0;
+ if (offset == getTextLength()) return getLineCount() - 1;
+
+ int bsResult = Arrays.binarySearch(myLineEnds, offset);
+ return bsResult >= 0 ? bsResult : -bsResult - 1;
+ }
+
+ public int getLineCount() {
+ return myLineEnds.length;
+ }
+
+ public int getTextLength() {
+ return myTextLength;
+ }
+
+ private void checkLineIndex(int index) {
+ if (index < 0 || index >= getLineCount()) {
+ throw new IndexOutOfBoundsException("Wrong line: " + index + ". Available lines count: " + getLineCount());
+ }
+ }
+ }
+
+ private static class LineOffsetsDocumentWrapper implements LineOffsets {
+ @NotNull private final Document myDocument;
+
+ public LineOffsetsDocumentWrapper(@NotNull Document document) {
+ myDocument = document;
+ }
+
+ @Override
+ public int getLineStart(int line) {
+ return myDocument.getLineStartOffset(line);
+ }
+
+ @Override
+ public int getLineEnd(int line) {
+ return myDocument.getLineEndOffset(line);
+ }
+
+ @Override
+ public int getLineNumber(int offset) {
+ return myDocument.getLineNumber(offset);
+ }
+
+ @Override
+ public int getLineCount() {
+ return DiffUtil.getLineCount(myDocument);
+ }
+
+ @Override
+ public int getTextLength() {
+ return myDocument.getTextLength();
+ }
+ }
+}
diff --git a/platform/diff-impl/src/com/intellij/diff/tools/util/text/SimpleThreesideTextDiffProvider.java b/platform/diff-impl/src/com/intellij/diff/tools/util/text/SimpleThreesideTextDiffProvider.java
index 3fffcdc1e4e6..779174cd4bc2 100644
--- a/platform/diff-impl/src/com/intellij/diff/tools/util/text/SimpleThreesideTextDiffProvider.java
+++ b/platform/diff-impl/src/com/intellij/diff/tools/util/text/SimpleThreesideTextDiffProvider.java
@@ -57,7 +57,7 @@ public class SimpleThreesideTextDiffProvider extends TextDiffProviderBase {
ComparisonPolicy comparisonPolicy = ignorePolicy.getComparisonPolicy();
List sequences = ContainerUtil.list(text1, text2, text3);
- List lineOffsets = ContainerUtil.map(sequences, LineOffsets::create);
+ List lineOffsets = ContainerUtil.map(sequences, LineOffsetsUtil::create);
indicator.checkCanceled();
List lineFragments = ComparisonManager.getInstance().compareLines(text1, text2, text3, comparisonPolicy, indicator);
diff --git a/platform/diff-impl/src/com/intellij/diff/util/DiffUtil.java b/platform/diff-impl/src/com/intellij/diff/util/DiffUtil.java
index 18c5915c1a7e..05257705b957 100644
--- a/platform/diff-impl/src/com/intellij/diff/util/DiffUtil.java
+++ b/platform/diff-impl/src/com/intellij/diff/util/DiffUtil.java
@@ -858,16 +858,7 @@ public class DiffUtil {
@NotNull
public static TextRange getLinesRange(@NotNull Document document, int line1, int line2, boolean includeNewline) {
- if (line1 == line2) {
- int lineStartOffset = line1 < getLineCount(document) ? document.getLineStartOffset(line1) : document.getTextLength();
- return new TextRange(lineStartOffset, lineStartOffset);
- }
- else {
- int startOffset = document.getLineStartOffset(line1);
- int endOffset = document.getLineEndOffset(line2 - 1);
- if (includeNewline && endOffset < document.getTextLength()) endOffset++;
- return new TextRange(startOffset, endOffset);
- }
+ return getLinesRange(LineOffsetsUtil.create(document), line1, line2, includeNewline);
}
@NotNull
diff --git a/platform/diff-impl/tests/com/intellij/diff/util/LineOffsetsTest.kt b/platform/diff-impl/tests/com/intellij/diff/util/LineOffsetsTest.kt
index dad6cc51773a..cd698d0db24a 100644
--- a/platform/diff-impl/tests/com/intellij/diff/util/LineOffsetsTest.kt
+++ b/platform/diff-impl/tests/com/intellij/diff/util/LineOffsetsTest.kt
@@ -16,7 +16,7 @@
package com.intellij.diff.util
import com.intellij.diff.DiffTestCase
-import com.intellij.diff.tools.util.text.LineOffsets
+import com.intellij.diff.tools.util.text.LineOffsetsUtil
import com.intellij.openapi.editor.impl.DocumentImpl
class LineOffsetsTest : DiffTestCase() {
@@ -40,26 +40,34 @@ class LineOffsetsTest : DiffTestCase() {
}
private fun checkSameAsDocument(text: String) {
- val lineOffsets = LineOffsets.create(text)
- val document = DocumentImpl(text)
+ val lineOffsets1 = LineOffsetsUtil.create(DocumentImpl(text))
+ val lineOffsets2 = LineOffsetsUtil.create(text)
- assertEquals(lineOffsets.lineCount, getLineCount(document))
- assertEquals(lineOffsets.textLength, document.textLength)
+ assertEquals(lineOffsets1.lineCount, lineOffsets2.lineCount)
+ assertEquals(lineOffsets1.textLength, lineOffsets2.textLength)
- for (i in 0 until lineOffsets.lineCount) {
- assertEquals(lineOffsets.getLineStart(i), document.getLineStartOffset(i))
- assertEquals(lineOffsets.getLineEnd(i), document.getLineEndOffset(i))
+ for (i in 0 until lineOffsets1.lineCount) {
+ assertEquals(lineOffsets1.getLineStart(i), lineOffsets2.getLineStart(i))
+ assertEquals(lineOffsets1.getLineEnd(i), lineOffsets2.getLineEnd(i))
+ }
+
+ for (i in 0..lineOffsets1.textLength) {
+ assertEquals(lineOffsets1.getLineNumber(i), lineOffsets2.getLineNumber(i))
}
}
private fun checkOffsets(text: String, vararg offsets: IntPair) {
- val lineOffsets = LineOffsets.create(text)
+ val lineOffsets = LineOffsetsUtil.create(text)
assertEquals(offsets.size, lineOffsets.lineCount)
- offsets.forEachIndexed { i, value ->
- assertEquals(lineOffsets.getLineStart(i), value.val1)
- assertEquals(lineOffsets.getLineEnd(i), value.val2)
+ offsets.forEachIndexed { line, value ->
+ assertEquals(lineOffsets.getLineStart(line), value.val1)
+ assertEquals(lineOffsets.getLineEnd(line), value.val2)
+
+ for (offset in lineOffsets.getLineStart(line)..lineOffsets.getLineEnd(line)) {
+ assertEquals(line, lineOffsets.getLineNumber(offset))
+ }
}
}
diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchType.java b/platform/dvcs-impl/src/com/intellij/dvcs/branch/BranchType.java
similarity index 75%
rename from plugins/git4idea/src/git4idea/branch/GitBranchType.java
rename to platform/dvcs-impl/src/com/intellij/dvcs/branch/BranchType.java
index a6e6fa641451..fda711b0a633 100644
--- a/plugins/git4idea/src/git4idea/branch/GitBranchType.java
+++ b/platform/dvcs-impl/src/com/intellij/dvcs/branch/BranchType.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2000-2016 JetBrains s.r.o.
+ * Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,8 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package git4idea.branch;
+package com.intellij.dvcs.branch;
-public enum GitBranchType {
- LOCAL, REMOTE
+import org.jetbrains.annotations.NotNull;
+
+public interface BranchType {
+
+ @NotNull
+ String getName();
}
diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchManager.java b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchManager.java
new file mode 100644
index 000000000000..c9af3ae983b9
--- /dev/null
+++ b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchManager.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.dvcs.branch;
+
+import com.intellij.dvcs.repo.AbstractRepositoryManager;
+import com.intellij.dvcs.repo.Repository;
+import com.intellij.openapi.util.text.StringUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+import static com.intellij.util.containers.ContainerUtil.map2List;
+import static com.intellij.util.containers.ContainerUtil.newArrayList;
+
+public abstract class DvcsBranchManager {
+ @NotNull private final AbstractRepositoryManager myRepositoryManager;
+ @NotNull private final DvcsBranchSettings myBranchSettings;
+ @NotNull public final BranchStorage myPredefinedFavoriteBranches = new BranchStorage();
+
+ protected DvcsBranchManager(@NotNull AbstractRepositoryManager repositoryManager,
+ @NotNull DvcsBranchSettings settings,
+ @NotNull BranchType[] branchTypes) {
+ myRepositoryManager = repositoryManager;
+ myBranchSettings = settings;
+ for (BranchType type : branchTypes) {
+ String defaultBranchName = getDefaultBranchName(type);
+ if (!StringUtil.isEmptyOrSpaces(defaultBranchName)) {
+ myPredefinedFavoriteBranches.myBranches.put(type.getName(), constructDefaultBranchPredefinedList(defaultBranchName));
+ }
+ }
+ }
+
+ @NotNull
+ private List constructDefaultBranchPredefinedList(@NotNull String defaultBranchName) {
+ List branchInfos = newArrayList(new DvcsBranchInfo("", defaultBranchName));
+ branchInfos.addAll(map2List(myRepositoryManager.getRepositories(),
+ repository -> new DvcsBranchInfo(repository.getRoot().getPath(), defaultBranchName)));
+ return branchInfos;
+ }
+
+ @Nullable
+ protected String getDefaultBranchName(@NotNull BranchType type) {return null;}
+
+ public boolean isFavorite(@Nullable BranchType branchType, @Nullable Repository repository, @NotNull String branchName) {
+ if (branchType == null) return false;
+ String branchTypeName = branchType.getName();
+ if (myBranchSettings.getFavorites().contains(branchTypeName, repository, branchName)) return true;
+ if (myBranchSettings.getExcludedFavorites().contains(branchTypeName, repository, branchName)) return false;
+ return myPredefinedFavoriteBranches.contains(branchTypeName, repository, branchName);
+ }
+
+ public void setFavorite(@Nullable BranchType branchType,
+ @Nullable Repository repository,
+ @NotNull String branchName,
+ boolean shouldBeFavorite) {
+ if (branchType == null) return;
+ String branchTypeName = branchType.getName();
+ if (shouldBeFavorite) {
+ myBranchSettings.getFavorites().add(branchTypeName, repository, branchName);
+ myBranchSettings.getExcludedFavorites().remove(branchTypeName, repository, branchName);
+ }
+ else {
+ if (myBranchSettings.getFavorites().contains(branchTypeName, repository, branchName)) {
+ myBranchSettings.getFavorites().remove(branchTypeName, repository, branchName);
+ }
+ else if (myPredefinedFavoriteBranches.contains(branchTypeName, repository, branchName)) {
+ myBranchSettings.getExcludedFavorites().add(branchTypeName, repository, branchName);
+ }
+ }
+ }
+}
diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchSettings.java b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchSettings.java
new file mode 100644
index 000000000000..cc0c3cb02376
--- /dev/null
+++ b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchSettings.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2000-2017 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.dvcs.branch;
+
+import com.intellij.util.xmlb.annotations.Tag;
+import org.jetbrains.annotations.NotNull;
+
+public class DvcsBranchSettings {
+ @Tag("favorite-branches")
+ private BranchStorage myFavoriteBranches = new BranchStorage();
+ @Tag("excluded-from-favorite")
+ private BranchStorage myExcludedFavorites = new BranchStorage();
+
+ @NotNull
+ public BranchStorage getFavorites() {
+ return myFavoriteBranches;
+ }
+
+ @NotNull
+ public BranchStorage getExcludedFavorites() {
+ return myExcludedFavorites;
+ }
+}
diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/ExternalProjectsManager.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/ExternalProjectsManager.java
index e6472e0f49a3..aea2cfc34737 100644
--- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/ExternalProjectsManager.java
+++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/ExternalProjectsManager.java
@@ -58,7 +58,7 @@ public class ExternalProjectsManager implements PersistentStateComponent {
private Area myCurArea;
private final List myAreas;
private final HackSearch myHackSearch;
- // EA-28497, EA-26379
- private final Getter myDebugDocumentTextGetter;
public StepIntersection(Convertor dataConvertor,
Convertor areasConvertor,
- final List areas,
- Getter debugDocumentTextGetter) {
+ final List areas) {
myAreas = areas;
- myDebugDocumentTextGetter = debugDocumentTextGetter;
myAreaIndex = 0;
myDataConvertor = dataConvertor;
myAreasConvertor = areasConvertor;
@@ -59,10 +53,6 @@ public class StepIntersection {
(o1, o2) -> o1.intersects(o2) ? 0 : o1.getStartOffset() < o2.getStartOffset() ? -1 : 1);
}
- public void resetIndex() {
- myAreaIndex = 0;
- }
-
public List process(final Iterable data) {
final List result = new ArrayList<>();
process(data, (data1, area) -> result.add(data1));
diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/CodeStyleMainPanel.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/CodeStyleMainPanel.java
index 8f715fd04cc5..de4f41a63d35 100644
--- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/CodeStyleMainPanel.java
+++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/CodeStyleMainPanel.java
@@ -18,11 +18,14 @@ package com.intellij.application.options.codeStyle;
import com.intellij.application.options.CodeStyleAbstractPanel;
import com.intellij.application.options.TabbedLanguageCodeStylePanel;
+import com.intellij.ide.DataManager;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.lang.Language;
+import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationBundle;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.options.ex.Settings;
import com.intellij.psi.codeStyle.CodeStyleScheme;
import com.intellij.psi.codeStyle.CodeStyleSchemes;
import com.intellij.ui.components.labels.SwingActionLink;
@@ -103,7 +106,12 @@ public class CodeStyleMainPanel extends JPanel implements TabbedLanguageCodeStyl
@Override
public void afterCurrentSettingsChanged() {
- mySchemesPanel.updateOnCurrentSettingsChange();
+ mySchemesPanel.updateOnCurrentSettingsChange();
+ DataContext context = DataManager.getInstance().getDataContext(mySettingsPanel);
+ Settings settings = Settings.KEY.getData(context);
+ if (settings != null) {
+ settings.revalidate();
+ }
}
@Override
diff --git a/platform/lang-impl/src/com/intellij/application/options/colors/ColorSchemeActions.java b/platform/lang-impl/src/com/intellij/application/options/colors/ColorSchemeActions.java
index 20f8de4e2a88..0f89dc4ef762 100644
--- a/platform/lang-impl/src/com/intellij/application/options/colors/ColorSchemeActions.java
+++ b/platform/lang-impl/src/com/intellij/application/options/colors/ColorSchemeActions.java
@@ -76,10 +76,16 @@ public abstract class ColorSchemeActions extends AbstractSchemeActions visibleHighlights = getVisibleHighlights(myStartOffset, myEndOffset, myProject, myEditor);
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionHintComponent.java b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionHintComponent.java
index 5b5509084a38..7526c62103e4 100644
--- a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionHintComponent.java
+++ b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionHintComponent.java
@@ -66,8 +66,6 @@ import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
import javax.swing.border.Border;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import java.awt.*;
@@ -142,12 +140,12 @@ public class IntentionHintComponent implements Disposable, ScrollAwareHint {
}
@NotNull
- public static IntentionHintComponent showIntentionHint(@NotNull final Project project,
- @NotNull PsiFile file,
- @NotNull final Editor editor,
- @NotNull ShowIntentionsPass.IntentionsInfo intentions,
- boolean showExpanded,
- @NotNull Point position) {
+ private static IntentionHintComponent showIntentionHint(@NotNull final Project project,
+ @NotNull PsiFile file,
+ @NotNull final Editor editor,
+ @NotNull ShowIntentionsPass.IntentionsInfo intentions,
+ boolean showExpanded,
+ @NotNull Point position) {
ApplicationManager.getApplication().assertIsDispatchThread();
final IntentionHintComponent component = new IntentionHintComponent(project, file, editor, intentions);
@@ -466,31 +464,28 @@ public class IntentionHintComponent implements Disposable, ScrollAwareHint {
myPopupShown = false;
}
});
- myPopup.addListSelectionListener(new ListSelectionListener() {
- @Override
- public void valueChanged(@NotNull ListSelectionEvent e) {
- final Object source = e.getSource();
- highlighter.dropHighlight();
- injectionHighlighter.dropHighlight();
-
- if (source instanceof DataProvider) {
- final Object selectedItem = PlatformDataKeys.SELECTED_ITEM.getData((DataProvider)source);
- if (selectedItem instanceof IntentionActionWithTextCaching) {
- final IntentionAction action = ((IntentionActionWithTextCaching)selectedItem).getAction();
- if (action instanceof SuppressIntentionActionFromFix) {
- if (injectedFile != null && ((SuppressIntentionActionFromFix)action).isShouldBeAppliedToInjectionHost() == ThreeState.NO) {
- final PsiElement at = injectedFile.findElementAt(injectedEditor.getCaretModel().getOffset());
- final PsiElement container = ((SuppressIntentionActionFromFix)action).getContainer(at);
- if (container != null) {
- injectionHighlighter.highlight(container, Collections.singletonList(container));
- }
+ myPopup.addListSelectionListener(e -> {
+ final Object source = e.getSource();
+ highlighter.dropHighlight();
+ injectionHighlighter.dropHighlight();
+
+ if (source instanceof DataProvider) {
+ final Object selectedItem = PlatformDataKeys.SELECTED_ITEM.getData((DataProvider)source);
+ if (selectedItem instanceof IntentionActionWithTextCaching) {
+ final IntentionAction action = ((IntentionActionWithTextCaching)selectedItem).getAction();
+ if (action instanceof SuppressIntentionActionFromFix) {
+ if (injectedFile != null && ((SuppressIntentionActionFromFix)action).isShouldBeAppliedToInjectionHost() == ThreeState.NO) {
+ final PsiElement at = injectedFile.findElementAt(injectedEditor.getCaretModel().getOffset());
+ final PsiElement container = ((SuppressIntentionActionFromFix)action).getContainer(at);
+ if (container != null) {
+ injectionHighlighter.highlight(container, Collections.singletonList(container));
}
- else {
- final PsiElement at = myFile.findElementAt(myEditor.getCaretModel().getOffset());
- final PsiElement container = ((SuppressIntentionActionFromFix)action).getContainer(at);
- if (container != null) {
- highlighter.highlight(container, Collections.singletonList(container));
- }
+ }
+ else {
+ final PsiElement at = myFile.findElementAt(myEditor.getCaretModel().getOffset());
+ final PsiElement container = ((SuppressIntentionActionFromFix)action).getContainer(at);
+ if (container != null) {
+ highlighter.highlight(container, Collections.singletonList(container));
}
}
}
@@ -515,12 +510,7 @@ public class IntentionHintComponent implements Disposable, ScrollAwareHint {
}
Disposer.register(this, myPopup);
- Disposer.register(myPopup, new Disposable() {
- @Override
- public void dispose() {
- ApplicationManager.getApplication().assertIsDispatchThread();
- }
- });
+ Disposer.register(myPopup, ApplicationManager.getApplication()::assertIsDispatchThread);
}
void canceled(@NotNull ListPopupStep intentionListStep) {
@@ -592,9 +582,7 @@ public class IntentionHintComponent implements Disposable, ScrollAwareHint {
@Override
@NotNull
public String getText() {
- return mySettings.isEnabled(myAction) ?
- CodeInsightBundle.message("disable.intention.action", myFamilyName) :
- CodeInsightBundle.message("enable.intention.action", myFamilyName);
+ return CodeInsightBundle.message(mySettings.isEnabled(myAction) ? "disable.intention.action" : "enable.intention.action", myFamilyName);
}
@Override
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionListStep.java b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionListStep.java
index 08b781cb3197..6276b4ef8271 100644
--- a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionListStep.java
+++ b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionListStep.java
@@ -100,12 +100,12 @@ public class IntentionListStep implements ListPopupStep {
HintManager.getInstance().hideAllHints();
if (myProject.isDisposed() || myEditor != null && myEditor.isDisposed()) return;
diff --git a/platform/lang-impl/src/com/intellij/diagnostic/logging/LogConsoleBase.java b/platform/lang-impl/src/com/intellij/diagnostic/logging/LogConsoleBase.java
index 7774d5955078..0730017f2234 100644
--- a/platform/lang-impl/src/com/intellij/diagnostic/logging/LogConsoleBase.java
+++ b/platform/lang-impl/src/com/intellij/diagnostic/logging/LogConsoleBase.java
@@ -27,6 +27,7 @@ import com.intellij.execution.ui.ConsoleView;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
@@ -303,8 +304,9 @@ public abstract class LogConsoleBase extends AdditionalTabComponent implements L
else {
try {
final BufferedReader reader = readerThread.myReader;
- while (reader != null && reader.ready()) {
- addMessage(reader.readLine());
+ while (reader.ready()) {
+ //ensure have read lock before requiring for sync, otherwise dispose() under write action would lead to deadlock
+ ReadAction.run(() -> addMessage(reader.readLine()));
}
}
catch (IOException ignore) {}
@@ -314,6 +316,7 @@ public abstract class LogConsoleBase extends AdditionalTabComponent implements L
}
protected synchronized void addMessage(final String text) {
+ if (myDisposed) return;
if (text == null) return;
if (myContentPreprocessor != null) {
final List fragments = myContentPreprocessor.parseLogLine(text + "\n");
diff --git a/platform/lang-impl/src/com/intellij/openapi/diff/impl/settings/DiffPreviewPanel.java b/platform/lang-impl/src/com/intellij/openapi/diff/impl/settings/DiffPreviewPanel.java
index a8f932379746..4cf6390f19c0 100644
--- a/platform/lang-impl/src/com/intellij/openapi/diff/impl/settings/DiffPreviewPanel.java
+++ b/platform/lang-impl/src/com/intellij/openapi/diff/impl/settings/DiffPreviewPanel.java
@@ -51,6 +51,7 @@ import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
import java.awt.*;
+import java.util.Arrays;
import java.util.List;
import static com.intellij.diff.tools.util.base.TextDiffSettingsHolder.TextDiffSettings;
@@ -108,8 +109,7 @@ class DiffPreviewPanel implements PreviewPanel {
private final List myContents;
public SampleRequest() {
- com.intellij.openapi.diff.DiffContent[] contents = DiffPreviewProvider.getContents();
- myContents = ContainerUtil.list(convert(contents[0]), convert(contents[1]), convert(contents[2]));
+ myContents = Arrays.asList(DiffPreviewProvider.getContents());
}
private static DiffContent convert(@NotNull com.intellij.openapi.diff.DiffContent content) {
diff --git a/platform/lang-impl/src/com/intellij/openapi/diff/impl/settings/DiffPreviewProvider.java b/platform/lang-impl/src/com/intellij/openapi/diff/impl/settings/DiffPreviewProvider.java
index 327f99ea60a0..6ad6cb479202 100644
--- a/platform/lang-impl/src/com/intellij/openapi/diff/impl/settings/DiffPreviewProvider.java
+++ b/platform/lang-impl/src/com/intellij/openapi/diff/impl/settings/DiffPreviewProvider.java
@@ -16,12 +16,14 @@
package com.intellij.openapi.diff.impl.settings;
-import com.intellij.openapi.diff.DiffContent;
-import com.intellij.openapi.diff.SimpleContent;
+import com.intellij.diff.DiffContentFactory;
+import com.intellij.diff.contents.DiffContent;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.Extensions;
+import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.StdFileTypes;
import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
/**
* @author oleg
@@ -30,19 +32,33 @@ import org.jetbrains.annotations.NonNls;
public abstract class DiffPreviewProvider {
public static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.diffPreviewProvider");
+ @NotNull
public abstract DiffContent[] createContents();
+ @NotNull
public static DiffContent[] getContents() {
// Assuming that standalone IDE should provide one provider
final DiffPreviewProvider[] providers = Extensions.getExtensions(EP_NAME);
- if (providers.length != 0){
+ if (providers.length != 0) {
return providers[0].createContents();
}
- return new DiffContent[]{createContent(LEFT_TEXT), createContent(CENTER_TEXT), createContent(RIGHT_TEXT)};
+ return createContent(LEFT_TEXT, CENTER_TEXT, RIGHT_TEXT, StdFileTypes.JAVA);
}
- private static SimpleContent createContent(String text) {
- return new SimpleContent(text, StdFileTypes.JAVA);
+ @NotNull
+ public static DiffContent[] createContent(@NotNull String left,
+ @NotNull String center,
+ @NotNull String right,
+ @NotNull FileType fileType) {
+ return new DiffContent[]{
+ createContent(left, StdFileTypes.JAVA),
+ createContent(center, StdFileTypes.JAVA),
+ createContent(right, StdFileTypes.JAVA)};
+ }
+
+ @NotNull
+ private static DiffContent createContent(@NotNull String text, @NotNull FileType fileType) {
+ return DiffContentFactory.getInstance().create(text, fileType);
}
@NonNls private static final String LEFT_TEXT = "class MyClass {\n" +
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java
index 8af1eb33f1a8..d3905ab5211f 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java
@@ -4265,7 +4265,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
editor.putUserData(LAST_PASTED_REGION, null);
EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
- LOG.assertTrue(pasteHandler instanceof EditorTextInsertHandler);
+ LOG.assertTrue(pasteHandler instanceof EditorTextInsertHandler, String.valueOf(pasteHandler));
((EditorTextInsertHandler)pasteHandler).execute(editor, editor.getDataContext(), () -> t);
TextRange range = editor.getUserData(LAST_PASTED_REGION);
diff --git a/platform/platform-impl/src/com/intellij/openapi/options/ex/Settings.java b/platform/platform-impl/src/com/intellij/openapi/options/ex/Settings.java
index 5b05bb8c4eaf..829f2e2dd63a 100644
--- a/platform/platform-impl/src/com/intellij/openapi/options/ex/Settings.java
+++ b/platform/platform-impl/src/com/intellij/openapi/options/ex/Settings.java
@@ -69,4 +69,9 @@ public abstract class Settings {
private static Configurable choose(Configurable configurable, Configurable variant) {
return variant != null ? variant : configurable;
}
+
+ /**
+ * Used to handle programmatic settings changes when no UI events are sent.
+ */
+ public void revalidate() {}
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/ConfigurableEditor.java b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/ConfigurableEditor.java
index d9b8cdee50ac..f6d0d8efb31a 100644
--- a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/ConfigurableEditor.java
+++ b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/ConfigurableEditor.java
@@ -187,7 +187,7 @@ class ConfigurableEditor extends AbstractEditor implements AnActionListener, AWT
}
}
- private void requestUpdate() {
+ void requestUpdate() {
final Configurable configurable = myConfigurable;
myQueue.queue(new Update(this) {
@Override
diff --git a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsEditor.java b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsEditor.java
index e828aa129760..64ba5a177a92 100644
--- a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsEditor.java
+++ b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsEditor.java
@@ -76,6 +76,11 @@ final class SettingsEditor extends AbstractEditor implements DataProvider {
myFilter.update(null, false, true);
return myTreeView.select(configurable);
}
+
+ @Override
+ public void revalidate() {
+ myEditor.requestUpdate();
+ }
};
mySearch = new SettingsSearch() {
@Override
diff --git a/platform/platform-impl/src/com/intellij/openapi/vcs/changes/TodoForRanges.java b/platform/platform-impl/src/com/intellij/openapi/vcs/changes/TodoForRanges.java
index f29de9d8e075..cf526a9c6bc1 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vcs/changes/TodoForRanges.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vcs/changes/TodoForRanges.java
@@ -20,7 +20,6 @@ import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
-import com.intellij.openapi.util.Getter;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vcs.checkin.StepIntersection;
@@ -74,8 +73,7 @@ public abstract class TodoForRanges {
public TextRange convert(TodoItemData o) {
return o.getTextRange();
}
- }, Convertor.SELF, myRanges, () -> ""
- );
+ }, Convertor.SELF, myRanges);
final List filtered = stepIntersection.process(Arrays.asList(todoItems));
final List> result = new ArrayList<>(filtered.size());
int offset = 0;
diff --git a/platform/platform-resources-en/src/messages/ApplicationBundle.properties b/platform/platform-resources-en/src/messages/ApplicationBundle.properties
index 7eeb6203ca41..e23c2cba26ec 100644
--- a/platform/platform-resources-en/src/messages/ApplicationBundle.properties
+++ b/platform/platform-resources-en/src/messages/ApplicationBundle.properties
@@ -782,5 +782,7 @@ settings.editor.scheme.copy.to.ide.title=Copy Project Scheme to IDE
settings.editor.scheme.copy.to.ide.label=IDE Scheme Name:
settings.editor.scheme.copy.to.project.title=Copy Setting to Project
settings.editor.scheme.copy.to.project.message=Overwrite project settings with values from {0}?
+settings.editor.scheme.import.success={0} was imported to {1} scheme.
+settings.editor.scheme.import.failure=Import failed: {0} is not a valid scheme.
title.save.code.style.scheme.as=Duplicate Code Style Scheme As
title.save.color.scheme.as=Duplicate Color Scheme As
\ No newline at end of file
diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml
index 44087d58a1a5..ba3cf23629dc 100644
--- a/platform/platform-resources/src/META-INF/LangExtensions.xml
+++ b/platform/platform-resources/src/META-INF/LangExtensions.xml
@@ -45,6 +45,7 @@
serviceImplementation="com.intellij.execution.filters.TextConsoleBuilderFactoryImpl"/>
+
diff --git a/platform/platform-tests/testData/diff/HugeFile.txt b/platform/platform-tests/testData/diff/HugeFile.txt
new file mode 100644
index 000000000000..090cc55c8597
--- /dev/null
+++ b/platform/platform-tests/testData/diff/HugeFile.txt
@@ -0,0 +1,146496 @@
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+ package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+ package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+ package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+ package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+ package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+ package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+ package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.*;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DataConstants;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.*;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.ex.util.EmptyHighlighter;
+import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
+import com.intellij.openapi.editor.impl.event.MarkupModelListener;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.util.IJSwingUtilities;
+import com.intellij.util.Key;
+import com.intellij.progress.ProgressManager;
+import org.jdom.Element;
+
+import javax.swing.*;
+import javax.swing.plaf.ScrollBarUI;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.*;
+import java.awt.font.TextHitInfo;
+import java.awt.im.InputMethodRequests;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.TooManyListenersException;
+
+public class EditorImpl implements EditorEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
+ private static final Key DND_COMMAND_KEY = new Key("DndCommand");
+ private final DocumentImpl myDocument;
+
+ private JPanel myPanel;
+ private JScrollPane myScrollPane;
+ private EditorComponentImpl myEditorComponent;
+ private EditorGutterComponentImpl myGutterComponent;
+
+ private Dimension mySize = null;
+ private CommandProcessor myCommandProcessor;
+ private MyScrollBar myVerticalScrollBar;
+ private MyScrollBar myHorizontalScrollBar;
+
+ private ArrayList myMouseListeners = new ArrayList();
+ private ArrayList myMouseMotionListeners = new ArrayList();
+
+ private int myCharHeight = -1;
+ private int myLineHeight = -1;
+ private int myDescent = -1;
+
+ private boolean isInsertMode = true;
+
+ private final CaretCursor myCaretCursor;
+ private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
+
+ private final Object MOUSE_DRAGGED_GROUP = new Key("MouseDraggedGroup");
+ private final Hashtable myUserDataMap = new Hashtable();
+
+ private final DocumentListener myEditorDocumentAdapter;
+
+ private EditorSettings mySettings;
+
+ private boolean isReleased = false;
+
+ private MouseEvent myMousePressedEvent = null;
+
+ private int mySavedSelectionStart = -1;
+ private int mySavedSelectionEnd = -1;
+ private int myLastColumnNumber = 0;
+
+ private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
+ private MyEditable myEditable;
+
+ private EditorColorsScheme myScheme;
+ private final boolean myIsViewer;
+ private final SelectionModelImpl mySelectionModel;
+ private final EditorMarkupModelImpl myMarkupModel;
+ private final FoldingModelImpl myFoldingModel;
+ private final ScrollingModelImpl myScrollingModel;
+ private final CaretModelImpl myCaretModel;
+
+ private static final RepaintCursorThread ourCaretThread;
+ private int myBorderStart;
+ private int myBorderEnd;
+ private int myBorderY;
+
+ private Color myBorderColor;
+
+ private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
+ private FoldRegion myMouseSelectedRegion = null;
+
+ private final static int MOUSE_SELECTION_STATE_NONE = 0;
+ private final static int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
+ private final static int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
+
+ private final MarkupModelListener myMarkupModelListener;
+
+ private Highlighter myHighlighter;
+
+ private int myScrollbarOrientation;
+ private boolean myMousePressedInsideSelection;
+ private FontMetrics myPlainFontMetrics;
+ private FontMetrics myBoldFontMetrics;
+ private FontMetrics myItalicFontMetrics;
+ private FontMetrics myBoldItalicFontMetrics;
+
+ private static final int CACHED_CHARS_BUFFER_SIZE = 300;
+
+ private CachedFontContent myPlainCache = new CachedFontContent(Font.PLAIN);
+ private CachedFontContent myBoldCache = new CachedFontContent(Font.BOLD);
+ private CachedFontContent myBoldItalicCache = new CachedFontContent(Font.BOLD + Font.ITALIC);
+ private CachedFontContent myItalicCache = new CachedFontContent(Font.ITALIC);
+
+ private int myCurrentFontType = Font.PLAIN;
+
+ private boolean myIsBlockSelectionMode;
+ private int myLongestLine;
+ private int myLongestLineLength;
+ private Runnable myCursorUpdater;
+ private Dimension myContentSize;
+ private int myCaretUpdateVShift;
+ Project myProject;
+ private long myMouseSelectionChangeTimestamp;
+ private int mySavedCaretOffsetForDNDUndoHack;
+ private ArrayList myFocusListeners = new ArrayList();
+
+ private MyInputMethodHandler myInputMethodRequestsHandler;
+ private InputMethodRequests myInputMethodRequestsSwingWrapper;
+ private boolean myIsOneLineMode;
+
+ static {
+ ourCaretThread = new RepaintCursorThread();
+ ourCaretThread.start();
+ }
+
+ public EditorImpl(DocumentEx document, boolean viewer, Project project) {
+ myProject = project;
+ myDocument = (DocumentImpl) document;
+ myScheme = new MyColorSchemeDelegate();
+ myIsViewer = viewer;
+ mySettings = new SettingsImpl(this);
+
+ mySelectionModel = new SelectionModelImpl(this);
+ myMarkupModel = new EditorMarkupModelImpl(this);
+ myFoldingModel = new FoldingModelImpl(this);
+ myCaretModel = new CaretModelImpl(this);
+ myIsBlockSelectionMode = false;
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+
+ myCommandProcessor = CommandProcessor.getInstance();
+
+ myEditorDocumentAdapter = new EditorDocumentAdapter();
+
+ myMarkupModelListener = new MarkupModelListener() {
+ public void rangeHighlighterChanged(MarkupModelEvent event) {
+ RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl) event.getHighlighter();
+ if (rangeHighlighter.isValid()) {
+ repaint(rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset());
+ } else {
+ repaint(0, getDocument().getTextLength());
+ }
+ ((EditorMarkupModelImpl) getMarkupModel()).repaint();
+ ((EditorMarkupModelImpl) getMarkupModel()).markDirtied();
+ GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
+ if (renderer != null) {
+ myGutterComponent.updateSize();
+ }
+ updateCaretCursor();
+ }
+ };
+
+ ((MarkupModelImpl) myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
+ ((MarkupModelImpl) getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
+
+ myDocument.addDocumentListener(myFoldingModel);
+ myDocument.addDocumentListener(myCaretModel);
+ myDocument.addDocumentListener(mySelectionModel);
+ myDocument.addDocumentListener(myEditorDocumentAdapter);
+
+ myCaretCursor = new CaretCursor();
+
+ myFoldingModel.flushCaretShift();
+ myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
+
+ initComponent();
+
+ Highlighter highlighter = new EmptyHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
+ setHighlighter(highlighter);
+
+ myScrollingModel = new ScrollingModelImpl(this);
+
+ myGutterComponent.updateSize();
+ validateSize();
+ }
+
+ public boolean isViewer() {
+ return myIsViewer;
+ }
+
+ public SelectionModel getSelectionModel() {
+ return mySelectionModel;
+ }
+
+ public MarkupModel getMarkupModel() {
+ return myMarkupModel;
+ }
+
+ public FoldingModel getFoldingModel() {
+ return myFoldingModel;
+ }
+
+ public CaretModel getCaretModel() {
+ return myCaretModel;
+ }
+
+ public ScrollingModel getScrollingModel() {
+ return myScrollingModel;
+ }
+
+ public EditorSettings getSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return mySettings;
+ }
+
+ public void reinitSettings() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myCharHeight = -1;
+ myLineHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ recalcSize();
+ myFoldingModel.refreshSettings();
+ myFoldingModel.rebuild();
+
+ if (myHighlighter instanceof EmptyHighlighter) {
+ ((EmptyHighlighter)myHighlighter).setAttributes(myScheme.getAttributes(HighlighterColors.TEXT));
+ }
+
+ myGutterComponent.revalidate();
+ myEditorComponent.repaint();
+
+ updateCaretCursor();
+ }
+
+ public void release() {
+ isReleased = true;
+ myDocument.removeDocumentListener(myHighlighter);
+ myDocument.removeDocumentListener(myEditorDocumentAdapter);
+ myDocument.removeDocumentListener(myFoldingModel);
+ myDocument.removeDocumentListener(myCaretModel);
+ myDocument.removeDocumentListener(mySelectionModel);
+ MarkupModelImpl markupModel = (MarkupModelImpl) myDocument.getMarkupModel(myProject, false);
+ if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
+ myMarkupModel.dispose();
+ myLineHeight = -1;
+ myCharHeight = -1;
+ myDescent = -1;
+ myPlainFontMetrics = null;
+ }
+
+ public void putUserData(Object key, Object value) {
+ if (value != null){
+ myUserDataMap.put(key, value);
+ }
+ else{
+ myUserDataMap.remove(key);
+ }
+ }
+
+ public Object getUserData(Object key) {
+ return myUserDataMap.get(key);
+ }
+
+ private void initComponent() {
+ myEditorComponent = new EditorComponentImpl(this);
+// myStatusBar = new EditorStatusBarImpl();
+
+ myScrollPane = new JScrollPane();
+ myPanel = new JPanel() {
+ public void addNotify() {
+ super.addNotify();
+ final JComponent parent = (JComponent)getParent();
+ if (parent.getBorder() != null) myScrollPane.setBorder(null);
+ }
+ };
+ //myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
+ myPanel.setLayout(new BorderLayout());
+
+ myVerticalScrollBar= new MyScrollBar(JScrollBar.VERTICAL);
+ myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
+ //
+ myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
+ myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
+ myScrollPane.setViewportView(myEditorComponent);
+ //myScrollPane.setBorder(null);
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+
+
+ myGutterComponent = new EditorGutterComponentImpl(this);
+ myGutterComponent.setOpaque(true);
+ myScrollPane.setRowHeaderView(myGutterComponent);
+ stopOptimizedScrolling();
+
+ myEditorComponent.setTransferHandler(new MyTransferHandler());
+ myEditorComponent.setAutoscrolls(true);
+
+/* Default mode till 1.4.0
+ * myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+ */
+ myPanel.add(myScrollPane);
+
+ myEditorComponent.addKeyListener (
+ new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ }
+
+ public void keyTyped(KeyEvent event) {
+ if(event.isConsumed()){
+ return;
+ }
+ char c = event.getKeyChar();
+ int modifiers = event.getModifiers();
+ if (((modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK)) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+ );
+
+ MyMouseAdapter mouseAdapter = new MyMouseAdapter();
+ myEditorComponent.addMouseListener(mouseAdapter);
+ myGutterComponent.addMouseListener(mouseAdapter);
+
+ MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
+ myEditorComponent.addMouseMotionListener(mouseMotionListener);
+ myGutterComponent.addMouseMotionListener(mouseMotionListener);
+
+ myEditorComponent.addFocusListener (
+ new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ myCaretCursor.activate();
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusGained();
+ }
+ public void focusLost(FocusEvent e) {
+ synchronized (ourCaretThread) {
+ if (ourCaretThread.myEditor == EditorImpl.this) {
+ ourCaretThread.myEditor = null;
+ }
+ }
+ int caretLine = getCaretModel().getLogicalPosition().line;
+ repaintLines(caretLine, caretLine);
+ fireFocusLost();
+ }
+ }
+ );
+
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+
+ try {
+ myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
+ public void drop(DropTargetDropEvent dtde) {
+ }
+
+ public void dragOver(DropTargetDragEvent dtde) {
+ Point location = dtde.getLocation();
+
+ moveCaretToScreenPos(location.x, location.y);
+ getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ });
+ } catch (TooManyListenersException e) {
+ }
+ }
+
+ private void processKeyTyped(char c) {
+ // [vova] This is patch for Mac OS X. Under Mac "input methods"
+ // is handled before our EventQueue consume upcoming KeyEvents.
+ IdeEventQueue queue = IdeEventQueue.getInstance();
+ if(queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()){
+ return;
+ }
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ DataContext dataContext = getDataContext();
+ actionManager.fireBeforeEditorTyping(c, dataContext);
+ EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
+ }
+
+ private void fireFocusLost() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusLost(this);
+ }
+ }
+
+ private FocusChangeListener[] getFocusListeners() {
+ return (FocusChangeListener[]) myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
+ }
+
+ private void fireFocusGained() {
+ FocusChangeListener[] listeners = getFocusListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ FocusChangeListener listener = listeners[i];
+ listener.focusGained(this);
+ }
+ }
+
+ public void setHighlighter(Highlighter highlighter) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (myHighlighter != null) {
+ getDocument().removeDocumentListener(myHighlighter);
+ }
+
+ getDocument().addDocumentListener(highlighter);
+ highlighter.setText(getDocument().getChars(), getDocument().getTextLength());
+ myHighlighter = highlighter;
+ myHighlighter.setEditor(this);
+ reinitSettings();
+ }
+
+ public Highlighter getHighlighter() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myHighlighter;
+ }
+
+ public JComponent getContentComponent() {
+ return myEditorComponent;
+ }
+
+ public EditorGutterComponent getGutterComponent() {
+ return myGutterComponent;
+ }
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ myPropertyChangeSupport.removePropertyChangeListener(listener);
+ }
+
+ public void setInsertMode(boolean val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ boolean oldValue = isInsertMode;
+ isInsertMode = val;
+ myPropertyChangeSupport.firePropertyChange(PROP_INSERT_MODE, oldValue, val);
+ //Repaint the caret line by moving caret to the same place
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ getCaretModel().moveToLogicalPosition(caretPosition);
+ }
+
+ public void setBlockSelectionMode(boolean isBlockSelectionMode) {
+ myIsBlockSelectionMode = isBlockSelectionMode;
+ }
+
+ public boolean isBlockSelectionMode() {
+ return myIsBlockSelectionMode;
+ }
+
+ public boolean isInsertMode() {
+ return isInsertMode;
+ }
+
+ private int yPositionToVisibleLineNumber(int y) {
+ return y/getLineHeight();
+ }
+
+ public static int getSpaceWidth(FontMetrics fontMetrics) {
+ int width = fontMetrics.charWidth(' ');
+ return width > 0 ? width : 1;
+ }
+
+ public VisualPosition xyToVisualPosition(Point p) {
+ int line = yPositionToVisibleLineNumber(p.y);
+
+ int x = 0;
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
+ int textLength = myDocument.getTextLength();
+
+ if (offset >= textLength) return new VisualPosition(line, 0);
+
+ int column = 0;
+ int prevX = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ char c = ' ';
+ IterationState state = new IterationState(this, offset, false);
+
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(fontMetrics);
+
+outer:
+ while (true) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ c = placeholder[j];
+ x += fontMetrics.charWidth(c);
+ if (x >= p.x) break outer;
+ column++;
+ }
+ offset = region.getEndOffset();
+ } else {
+ prevX = x;
+ c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ } else {
+ x += fontMetrics.charWidth(c);
+ }
+
+ if (x >= p.x) break;
+
+ if (c == '\t') {
+ column += (x - prevX) / spaceSize;
+ } else {
+ column++;
+ }
+
+ offset++;
+ }
+ }
+
+ int charWidth = fontMetrics.charWidth(c);
+
+ if (x >= p.x && c == '\t') {
+ if (mySettings.isCaretInsideTabs()) {
+ column += (p.x - prevX) / spaceSize;
+ if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
+ } else if ((x - p.x) *2 < x - prevX) {
+ column += (x - prevX) / spaceSize;
+ }
+ } else if (x >= p.x) {
+ if ((x - p.x) * 2 < charWidth) column++;
+ } else {
+ column += (p.x - x) / getSpaceWidth(fontMetrics);
+ }
+
+ return new VisualPosition(line, column);
+ }
+
+ public VisualPosition offsetToVisualPosition(int offset) {
+ return logicalToVisualPosition(offsetToLogicalPosition(offset));
+ }
+
+ public LogicalPosition offsetToLogicalPosition(int offset) {
+ int line = calcLogicalLineNumber(offset);
+ int column = calcColumnNumber(offset, line);
+ return new LogicalPosition(line, column);
+ }
+
+ public LogicalPosition xyToLogicalPosition(Point p) {
+ final Point pp;
+ if (p.x >= 0 && p.y >= 0) {
+ pp = p;
+ } else {
+ pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
+ }
+
+ return visualToLogicalPosition(xyToVisualPosition(pp));
+ }
+
+ public Point logicalPositionToXY(LogicalPosition pos) {
+ VisualPosition visible = logicalToVisualPosition(pos);
+ int y = visibleLineNumberToYPosition(visible.line);
+
+ int lineStartOffset;
+
+ if (pos.line == 0) {
+ lineStartOffset = 0;
+ } else if (pos.line >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(pos.line);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ public Point visualPositionToXY(VisualPosition visible) {
+ int y = visibleLineNumberToYPosition(visible.line);
+ int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
+
+ int lineStartOffset;
+
+ if (logLine == 0) {
+ lineStartOffset = 0;
+ } else if (logLine >= myDocument.getLineCount()) {
+ lineStartOffset = myDocument.getTextLength();
+ } else {
+ lineStartOffset = myDocument.getLineStartOffset(logLine);
+ }
+
+ int x = getTabbedTextWidth(lineStartOffset, visible);
+ return new Point(x, y);
+ }
+
+ private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
+ if (pos.column == 0) return 0;
+
+ int x = 0;
+ int offset = lineStartOffset;
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int textLength = myDocument.getTextLength();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ int spaceSize = getSpaceWidth(getFontMetrics(myCurrentFontType));
+
+ int column = 0;
+outer:
+ while (column < pos.column) {
+ if (offset >= textLength) break;
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion region = state.getCurrentFold();
+
+ if (region != null) {
+ char placeholder[] = region.getPlaceholderText().toCharArray();
+ for (int j = 0; j < placeholder.length; j++) {
+ x += fontMetrics.charWidth(placeholder[j]);
+ column++;
+ if (column >= pos.column) break outer;
+ }
+ offset = region.getEndOffset();
+ } else {
+ int c = text[offset];
+ if (c == '\n') {
+ break;
+ } else if (c == '\t') {
+ int prevX = x;
+ x = nextTabStop(x);
+ column += (x - prevX) / spaceSize;
+ } else {
+ x += fontMetrics.charWidth(c);
+ column++;
+ }
+ offset++;
+ }
+ }
+
+ if (column != pos.column) {
+ x += getSpaceWidth(fontMetrics) * (pos.column - column);
+ }
+
+ return x;
+ }
+
+ public int nextTabStop(int x) {
+ int tabSize = mySettings.getTabSize(myProject);
+ if (tabSize <= 0) {
+ tabSize = 1;
+ }
+
+ tabSize *= getSpaceWidth(getFontMetrics(Font.PLAIN));
+
+ int nTabs = x / tabSize;
+ return (nTabs + 1) * tabSize;
+ }
+
+ public int visibleLineNumberToYPosition(int lineNum) {
+ if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
+ return lineNum*getLineHeight();
+ }
+
+ public void repaint(int startOffset, int endOffset) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myScrollPane == null)
+ return;
+ if(endOffset > myDocument.getTextLength()) {
+ endOffset = myDocument.getTextLength();
+ }
+ if(startOffset < endOffset) {
+ int startLine = myDocument.getLineNumber(startOffset);
+ int endLine = myDocument.getLineNumber(endOffset);
+ repaintLines(startLine, endLine);
+ }
+ }
+
+ public void repaintLines(int startLine, int endLine) {
+ Rectangle visibleRect = getScrollingModel().getVisibleArea();
+ int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
+ int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
+
+ myEditorComponent.repaintEditorComponent(visibleRect.x,
+ yStartLine,
+ visibleRect.x + visibleRect.width,
+ yEndLine - yStartLine);
+ myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
+ }
+
+ private void beforeChangedUpdate(DocumentEvent e) {
+ int oldStartVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int oldEndVisualLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
+
+ if (e.getOldLength() > 0 && oldStartVisualLine <= myLongestLine && myLongestLine <= oldEndVisualLine) {
+ myLongestLine = -1;
+ myLongestLineLength = -1;
+ } else if (myLongestLine >= oldStartVisualLine) {
+ myLongestLine -= (oldEndVisualLine - oldStartVisualLine);
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
+ myCaretUpdateVShift = pos.y - viewRect.y;
+ }
+
+ private void changedUpdate(DocumentEvent e) {
+ if(myScrollPane == null) return;
+
+ stopOptimizedScrolling();
+ mySelectionModel.removeBlockSelection();
+
+ int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
+ int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
+
+ if (myLongestLine >= 0) {
+ if (startVisualLine <= myLongestLine) {
+ if (myLongestLine <= endVisualLine && e.getOldLength() > 0) {
+ myLongestLineLength = -1;
+ } else {
+ myLongestLine += (endVisualLine - startVisualLine);
+ }
+ }
+
+ if (myDocument.getLineCount() == 0) {
+ recalcSize();
+ }
+ else {
+ recalcSizeInRange(myDocument.getLineStartOffset(myDocument.getLineNumber(e.getOffset())),
+ myDocument.getLineEndOffset(myDocument.getLineNumber(e.getOffset() + e.getNewLength())),
+ myLongestLineLength, startVisualLine);
+
+ }
+ } else {
+ recalcSize();
+ }
+
+ updateCaretCursor();
+
+ repaintLines(startVisualLine, endVisualLine);
+
+ Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
+ int scrollOffset = caretLocation.y - myCaretUpdateVShift;
+ getScrollingModel().scrollVertically(scrollOffset);
+ }
+
+ private void validateSize() {
+ if (myLongestLine < 0) recalcSize();
+
+ int documentWidth = getLineWidth(myLongestLine);
+
+ int contentWidth = calcContentWidth(documentWidth);
+ int contentHeight = Math.max(getLineHeight(), calcContentHeight());
+
+ Rectangle viewRectangle = getScrollingModel().getVisibleArea();
+
+ if (myContentSize == null || contentWidth != myContentSize.width || contentHeight != myContentSize.height) {
+ int width = Math.max(contentWidth, viewRectangle.width);
+ int height = Math.max(contentHeight, viewRectangle.height);
+ mySize = new Dimension(width, height);
+ myContentSize = new Dimension(contentWidth, contentHeight);
+
+ stopOptimizedScrolling();
+ myEditorComponent.setSize(mySize);
+ myScrollPane.getVerticalScrollBar().setMaximum(mySize.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(mySize.width);
+
+ int lineNum = Math.max(1, getDocument().getLineCount());
+ myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth("" + (lineNum + 2)) + 6);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myEditorComponent.repaint();
+ myScrollPane.repaint();
+ myMarkupModel.repaint();
+ }
+ }
+
+ private int calcContentHeight() {
+ return getLineHeight() * (getVisibleLineCount() + mySettings.getAdditionalLinesCount());
+ }
+
+ private int calcContentWidth(int documentWidth) {
+ int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
+ return Math.max(caretX, documentWidth) +
+ mySettings.getAdditionalColumnsCount() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ void recalcSizeAndRepaint() {
+ recalcSize();
+ validateSize();
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myGutterComponent.setSize(myGutterComponent.getPreferredSize());
+
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+
+ myMarkupModel.repaint();
+
+ stopOptimizedScrolling();
+ myEditorComponent.repaintEditorComponent();
+
+ myGutterComponent.repaint();
+ }
+
+ public Document getDocument() {
+ return myDocument;
+ }
+
+ public JComponent getComponent() {
+ return myPanel;
+ }
+
+ public void addEditorMouseListener(EditorMouseListener listener) {
+ myMouseListeners.add(listener);
+ }
+
+ public void removeEditorMouseListener(EditorMouseListener listener) {
+ boolean success = myMouseListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ myMouseMotionListeners.add(listener);
+ }
+
+ public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
+ boolean success = myMouseMotionListeners.remove(listener);
+ LOG.assertTrue(success);
+ }
+
+ public void paint(Graphics g) {
+ myEditorComponent.setOpaque(true);
+
+ flushDeferredSizeChanges();
+
+ Rectangle clip = getClipBounds(g);
+
+ if(clip == null) {
+ return;
+ }
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(viewRect == null) {
+ return;
+ }
+
+ if(isReleased) {
+ g.setColor(new Color(128, 255, 128));
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ return;
+ }
+
+ Color background = myScheme.getColor(EditorColors.BACKGROUND_COLOR);
+ g.setColor(background == null ? Color.white : background);
+ g.fillRect(clip.x, clip.y, clip.width, clip.height);
+
+ paintBackgrounds(g, clip);
+ paintRightMargin(g, clip);
+ paintLineMarkersSeparators(g, clip, myDocument.getMarkupModel(myProject));
+ paintLineMarkersSeparators(g, clip, myMarkupModel);
+ paintText(g, clip);
+ paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
+
+ paintCaretCursor(g);
+
+ paintComposedTextDecoration((Graphics2D) g);
+ }
+
+ private void paintComposedTextDecoration(Graphics2D g) {
+ if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
+ VisualPosition visStart = offsetToVisualPosition(Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
+ int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
+ Point p1 = visualPositionToXY(visStart);
+ Point p2 = logicalPositionToXY(offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
+
+ Stroke saved = g.getStroke();
+ BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2,0,2}, 0);
+ g.setStroke(dotted);
+ g.drawLine(p1.x, y, p2.x, y);
+ g.setStroke(saved);
+ }
+ }
+
+ public void flushDeferredSizeChanges() {
+ if (myCursorUpdater != null) {
+ myCursorUpdater.run();
+ }
+ }
+
+ private Rectangle getClipBounds(Graphics g) {
+ return mySettings.isWrapAtRightMargin() ? new Rectangle(mySize) : g.getClipBounds();
+ }
+
+ private void paintRightMargin(Graphics g, Rectangle clip) {
+ Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
+ if(!mySettings.isRightMarginShown() || rightMargin == null) {
+ return;
+ }
+ int x = mySettings.getRightMargin()*getSpaceWidth(getFontMetrics(Font.PLAIN));
+ if(x >= clip.x && x < clip.x+clip.width) {
+ g.setColor(rightMargin);
+ g.drawLine(x, clip.y, x, clip.y+clip.height);
+ }
+ }
+
+ private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
+ int startLineNumber = yPositionToVisibleLineNumber(clip.y);
+ int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
+
+ RangeHighlighter[] segmentHighlighters;
+ segmentHighlighters = myDocument.getMarkupModel(myProject).getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighters[i], startLineNumber,
+ endLineNumber
+ );
+ }
+
+ segmentHighlighters = getMarkupModel().getAllHighlighters();
+ for (int i = 0; i < segmentHighlighters.length; i++) {
+ RangeHighlighter segmentHighlighter = segmentHighlighters[i];
+ paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx) segmentHighlighter, startLineNumber, endLineNumber);
+ }
+ }
+
+ private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
+ int startLineNumber, int endLineNumber
+ ) {
+ if (!segmentHighlighter.isValid()) {
+ return;
+ }
+ if (segmentHighlighter.isAfterEndOfLine()) {
+ int startOffset = segmentHighlighter.getStartOffset();
+ int visibleStartLine = offsetToVisualPosition(startOffset).line;
+
+ if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
+ if(visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
+ int logStartLine = offsetToLogicalPosition(startOffset).line;
+ LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
+ Point end = logicalPositionToXY(logPosition);
+ int charWidth = getSpaceWidth(getFontMetrics(Font.PLAIN));
+ int lineHeight = getLineHeight();
+ TextAttributes attributes = segmentHighlighter.getTextAttributes();
+ if(attributes != null && attributes.getBackgroundColor() != null) {
+ g.setColor(attributes.getBackgroundColor());
+ g.fillRect(end.x, end.y, charWidth, lineHeight);
+ }
+ if(attributes != null && attributes.getEffectColor() != null) {
+ int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
+ g.setColor(attributes.getEffectColor());
+ if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
+ drawWave(g, end.x, end.x + charWidth - 1, y);
+ } else {
+ g.drawLine(end.x, y, end.x + charWidth - 1, y);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getLineWidth(int visualLine) {
+ if (visualLine < 0) return 0;
+
+ if (visualLine == myLongestLine && myLongestLineLength >= 0) return myLongestLineLength;
+
+ int x = 0;
+
+ int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(visualLine, 0)));
+ return getLineTailWidth(offset, x, visualLine);
+ }
+
+ private int getLineTailWidth(int offset, int x, int visualLine) {
+ int end = myDocument.getTextLength();
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ break;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+
+ if (myLongestLine == visualLine) myLongestLineLength = x;
+
+ return x;
+ }
+
+ public int getMaxWidthInRange(int startOffset, int endOffset) {
+ int width = 0;
+ VisualPosition start = offsetToVisualPosition(startOffset);
+ VisualPosition end = offsetToVisualPosition(endOffset);
+
+ for (int i = start.line; i <= end.line; i++) {
+ int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
+ int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
+
+ if (lineWidth > width) {
+ width = lineWidth;
+ }
+ }
+
+ return width;
+ }
+
+ private void paintBackgrounds(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color backColor = attributes.getBackgroundColor();
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ int fontType = attributes.getFontType();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
+ while (!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if (hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+
+ if (lIterator.getLineNumber() < lastLineIndex) {
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ } else {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ break;
+ }
+
+ position.x = 0;
+ if (position.y > clip.y + clip.height) break;
+ position.y += lineHeight;
+ start = lEnd;
+ }
+
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText().toCharArray(), 0,
+ collapsedFolderAt.getPlaceholderText().length(), position, fontType
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength() - start,
+ position, fontType
+ );
+ } else {
+ position.x = drawBackground(g, backColor, text, start, hEnd - start, position, fontType);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ backColor = attributes.getBackgroundColor();
+ fontType = attributes.getFontType();
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
+ paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
+ }
+ }
+
+ private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
+ Rectangle clip, int lineHeight
+ ) {
+ Color backColor = iterationState.getPastFileEndBackground();
+ if (backColor != null) {
+ Color saved = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
+ g.setColor(saved);
+ }
+ }
+
+ private int drawBackground(Graphics g, Color backColor, char[] text, int offset, int length, Point position, int fontType) {
+ int w = getTextSegmentWidth(text, offset, length, position.x, fontType);
+
+ if(backColor != null) {
+ Color savedColor = g.getColor();
+ g.setColor(backColor);
+ g.fillRect(position.x, position.y, w, getLineHeight());
+ g.setColor(savedColor);
+ }
+
+ return position.x + w;
+ }
+
+ private class LineIteratorWrapper implements LineIterator {
+ private int myRightMargin;
+ private int myCurrentWidth;
+ private int myEnd;
+ private int myStart;
+
+ public LineIteratorWrapper() {
+ myRightMargin = mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN));
+ }
+
+ public void start(int startOffset) {
+ myStart = myDocument.getLineStartOffset(myDocument.getLineNumber(startOffset));
+ myEnd = myStart;
+ myCurrentWidth = 0;
+ advanceOffset();
+ }
+
+ private void advanceOffset() {
+ if (atEnd()) return;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ myStart = myEnd;
+
+outer:
+ while (myEnd < myDocument.getTextLength()) {
+ char c = text[myEnd];
+
+ if (c == '\t') {
+ myCurrentWidth = nextTabStop(myCurrentWidth);
+ } else if (c == '\n') {
+ myEnd++;
+ break outer;
+ } else {
+ myCurrentWidth += getFontMetrics(Font.PLAIN).charWidth(c);
+ }
+
+ myEnd++;
+
+ if (myCurrentWidth > myRightMargin) {
+ int savedEnd = myEnd;
+ while (myEnd > myStart) {
+ myEnd--;
+ if (Character.isSpaceChar(text[myEnd]) || Character.isWhitespace(text[myEnd])) break outer;
+ }
+ myEnd = savedEnd;
+
+ while (myCurrentWidth > myRightMargin) {
+ myEnd--;
+ myCurrentWidth -= getFontMetrics(Font.PLAIN).charWidth(text[myEnd]);
+ }
+ }
+ }
+
+ myCurrentWidth = 0;
+ }
+
+ public int getStart() {
+ return myStart;
+ }
+
+ public int getEnd() {
+ return Math.min(myEnd, myDocument.getTextLength());
+ }
+
+ public int getSeparatorLength() {
+ if (myEnd == 0) return 0;
+ return myDocument.getCharsNoThreadCheck()[myEnd - 1] == '\n' ? 1 : 0;
+ }
+
+ public int getLineNumber() {
+ return 0;
+ }
+
+ public void advance() {
+ advanceOffset();
+ }
+
+ public boolean atEnd() {
+ return myEnd >= myDocument.getTextLength();
+ }
+ }
+
+ private LineIterator createLineIterator() {
+ if (mySettings.isWrapAtRightMargin()) {
+ return new LineIteratorWrapper();
+ }
+
+ return myDocument.createLineIterator();
+ }
+
+ private void paintText(Graphics g, Rectangle clip) {
+ int lineHeight = getLineHeight();
+
+ int visibleLineNumber = clip.y/lineHeight;
+
+ int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
+
+ if(startLineNumber >= myDocument.getLineCount() || startLineNumber < 0)
+ return;
+
+ int start = myDocument.getLineStartOffset(startLineNumber);
+
+ IterationState iterationState = new IterationState(this, start, true);
+
+ LineIterator lIterator = createLineIterator();
+ lIterator.start(start);
+ if(lIterator.atEnd())
+ return;
+
+ TextAttributes attributes = iterationState.getMergedAttributes();
+ Color currentColor = attributes.getForegroundColor();
+ if(currentColor == null) {
+ currentColor = Color.black;
+ }
+ Color effectColor = attributes.getEffectColor();
+ EffectType effectType = attributes.getEffectType();
+ int fontType = attributes.getFontType();
+ myCurrentFontType = fontType;
+ Font currentFont = getFont(fontType);
+ g.setColor(currentColor);
+ g.setFont(currentFont);
+ Point position = new Point(0, visibleLineNumber * lineHeight);
+ while(!iterationState.atEnd() && !lIterator.atEnd()) {
+ int hEnd = iterationState.getEndOffset();
+ int lEnd = lIterator.getEnd();
+
+ if(hEnd >= lEnd) {
+ FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
+ if (collapsedFolderAt == null) {
+ drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ position.x = 0;
+ position.y += lineHeight;
+ if (position.y > clip.y + clip.height + lineHeight) break;
+ start = lEnd;
+ }
+
+ if (myBorderColor != null) flushBorder(g);
+ lIterator.advance();
+ } else {
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
+ fontType, currentColor
+ );
+ } else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
+ position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
+ effectType, fontType, currentColor
+ );
+ } else {
+ position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
+ }
+
+ iterationState.advance();
+ attributes = iterationState.getMergedAttributes();
+ Color color = attributes.getForegroundColor();
+ if(color == null) {
+ color = Color.black;
+ }
+ if(color != currentColor) {
+ g.setColor(color);
+ currentColor = color;
+ }
+ effectColor = attributes.getEffectColor();
+ effectType = attributes.getEffectType();
+ fontType = attributes.getFontType();
+
+ start = iterationState.getStartOffset();
+ }
+ }
+
+ FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
+ if (collapsedFolderAt != null) {
+ drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType, currentColor);
+ flushBorder(g);
+ }
+
+ flushCachedChars(g);
+ }
+
+ private class CachedFontContent {
+ final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
+ final int[] start = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] length = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
+ final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
+ final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
+
+ int myCount = 0;
+ final int myFontType;
+
+ public CachedFontContent(int fontType) {
+ myFontType = fontType;
+ }
+
+ public void flushContent(Graphics g) {
+ if (myCount != 0) {
+ if (myCurrentFontType != myFontType) {
+ myCurrentFontType = myFontType;
+ g.setFont(getFont(myFontType));
+ }
+
+ Color currentColor = null;
+ for (int i = 0; i < myCount; i++) {
+ if (!color[i].equals(currentColor)) {
+ currentColor = color[i];
+ g.setColor(currentColor);
+ }
+
+ g.drawChars(data[i], start[i], length[i], x[i], y[i]);
+ color[i] = null;
+ data[i] = null;
+ }
+
+ myCount = 0;
+ }
+ }
+
+ public void addContent(Graphics g, char[] data, int start, int length, int x, int y, Color color) {
+ this.data[myCount] = data;
+ this.start[myCount] = start;
+ this.length[myCount] = length;
+ this.x[myCount] = x;
+ this.y[myCount] = y;
+ this.color[myCount] = color;
+
+ myCount++;
+ if (myCount >= CACHED_CHARS_BUFFER_SIZE) {
+ flushContent(g);
+ }
+ }
+ }
+
+ private void flushCachedChars(Graphics g) {
+ myPlainCache.flushContent(g);
+ myBoldCache.flushContent(g);
+ myBoldItalicCache.flushContent(g);
+ myItalicCache.flushContent(g);
+ }
+
+ private void paintCaretCursor(Graphics g) {
+ myCaretCursor.paint(g);
+ }
+
+ private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
+ RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
+ for (int i = 0; i < lineMarkers.length; i++) {
+ RangeHighlighter lineMarker = lineMarkers[i];
+ paintLineMarkerSeparator(lineMarker, clip, g);
+ }
+ }
+
+ private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
+ if (!marker.isValid()) {
+ return;
+ }
+ Color separatorColor = marker.getLineSeparatorColor();
+ if (separatorColor != null) {
+ int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
+ .getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
+ if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
+ return;
+ }
+
+ int endShift = clip.x + clip.width;
+ g.setColor(separatorColor);
+
+ if (mySettings.isRightMarginShown()
+ && myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
+ endShift = Math.min(endShift, mySettings.getRightMargin() * getSpaceWidth(getFontMetrics(Font.PLAIN)));
+ }
+
+ int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
+
+ if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
+ y += getLineHeight();
+ }
+
+ g.drawLine(0, y - 1, endShift, y - 1);
+ }
+ }
+
+ private Font getFont(int type) {
+ if(type == Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD);
+ else if(type == Font.ITALIC)
+ return myScheme.getFont(EditorFontType.ITALIC);
+ else if(type == Font.ITALIC + Font.BOLD)
+ return myScheme.getFont(EditorFontType.BOLD_ITALIC);
+ else
+ return myScheme.getFont(EditorFontType.PLAIN);
+ }
+
+ private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ if (start >= end) return position.x;
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text, start, end - start, x, y, effectColor, effectType, fontType, fontColor);
+ }
+
+ private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
+
+ if (!isInClip) return position.x;
+
+ int y = getLineHeight() - getDescent() + position.y;
+ int x = position.x;
+ return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType,
+ fontColor
+ );
+ }
+
+ private int drawTabbedString(Graphics g, char[] text, int offset, int length, int x, int y, Color effectColor,
+ EffectType effectType, int fontType, Color fontColor
+ ) {
+ int xStart = x;
+
+ int start = offset;
+
+ for (int i = offset; i < offset + length; i++) {
+ if (text[i] != '\t') continue;
+
+ if (i > start) {
+ drawCharsCached(g, text, start, i - start, x, y, fontType, fontColor);
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ drawCharsCached(g, text, start, offset + length - start, x, y, fontType, fontColor);
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+
+ if(effectColor != null) {
+ Color savedColor = g.getColor();
+
+ int w = getTextSegmentWidth(text, offset, length, xStart, fontType);
+ if (effectType == EffectType.LINE_UNDERSCORE) {
+ g.setColor(effectColor);
+ g.drawLine(xStart, y+1, xStart+w, y+1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.STRIKEOUT) {
+ g.setColor(effectColor);
+ int y1 = y-getCharHeight() / 2;
+ g.drawLine(xStart, y1, xStart+w, y1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.WAVE_UNDERSCORE) {
+ g.setColor(effectColor);
+ drawWave(g, xStart, xStart + w, y + 1);
+ g.setColor(savedColor);
+ } else if (effectType == EffectType.BOXED) {
+ if (myBorderStart == -1) {
+ if (myBorderColor != effectColor) {
+ flushBorder(g);
+ myBorderStart = xStart;
+ }
+ myBorderY = y - getLineHeight() + getDescent();
+ myBorderColor = effectColor;
+ }
+ myBorderEnd = xStart + w;
+ }
+ } else {
+ flushBorder(g);
+ }
+
+ return x;
+ }
+
+ private void drawCharsCached(Graphics g, char[] data, int start, int length, int x, int y, int fontType, Color color) {
+ if (fontType == myCurrentFontType) {
+ g.drawChars(data, start, length, x, y);
+ } else if (fontType == Font.PLAIN) {
+ myPlainCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.BOLD) {
+ myBoldCache.addContent(g, data, start, length, x, y, color);
+ } else if (fontType == Font.ITALIC) {
+ myItalicCache.addContent(g, data, start, length, x, y, color);
+ } else {
+ myBoldItalicCache.addContent(g, data, start, length, x, y, color);
+ }
+ }
+
+ private void flushBorder(Graphics g) {
+ if (myBorderStart != -1) {
+ Color savedColor = g.getColor();
+ g.setColor(myBorderColor);
+ g.drawRect(myBorderStart, myBorderY, myBorderEnd - myBorderStart - 1, getLineHeight() - 1);
+ g.setColor(savedColor);
+ myBorderStart = -1;
+ myBorderEnd = -1;
+ myBorderY = 0;
+ myBorderColor = null;
+ }
+ }
+
+ private static final int WAVE_HEIGHT = 2;
+ private static final int WAVE_SEGMENT_LENGTH = 4;
+
+ private void drawWave(Graphics g, int xStart, int xEnd, int y) {
+ int startSegment = xStart / WAVE_SEGMENT_LENGTH;
+ int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
+ for (int i = startSegment; i < endSegment; i++) {
+ drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
+ }
+
+ int x = WAVE_SEGMENT_LENGTH * endSegment;
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ }
+
+ private static void drawWaveSegment(Graphics g, int x, int y) {
+ g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
+ g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
+ }
+
+ private int getTextSegmentWidth(char[]text, int offset, int length, int xStart, int fontType) {
+ int start = offset;
+ int x = xStart;
+
+ for(int i=offset; i start) {
+ for (int j = start; j < i; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ x = nextTabStop(x);
+ start = i+1;
+ }
+
+ if(offset+length > start) {
+ for (int j = start; j < offset + length; j++) x += getFontMetrics(fontType).charWidth(text[j]);
+ }
+ return x - xStart;
+ }
+
+ public int getLineHeight() {
+ if(myLineHeight != -1) return myLineHeight;
+
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myLineHeight = (int)(fontMetrics.getHeight()*myScheme.getLineSpacing());
+ if(myLineHeight == 0) {
+ myLineHeight = fontMetrics.getHeight();
+ if(myLineHeight == 0) {
+ myLineHeight = 12;
+ }
+ }
+
+/*
+ recalcSize(0, myDocument.getTextLength());
+
+ Dimension size = getPreferredSize();
+ myEditorComponent.setSize(size);
+ myScrollPane.getVerticalScrollBar().setMaximum(size.height);
+ myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
+ myScrollPane.revalidate();
+ // myErrorPanel.revalidate();
+ myEditorComponent.repaintEditorComponent();
+ myGutterComponent.repaint();
+*/
+
+ return myLineHeight;
+ }
+
+ int getDescent() {
+ if (myDescent != -1)
+ return myDescent;
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myDescent = fontMetrics.getDescent();
+ return myDescent;
+ }
+
+ public FontMetrics getFontMetrics(int fontType) {
+ if (myPlainFontMetrics == null) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
+ myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
+ myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
+ }
+
+ if (fontType == Font.PLAIN) return myPlainFontMetrics;
+ if (fontType == Font.BOLD) return myBoldFontMetrics;
+ if (fontType == Font.ITALIC) return myItalicFontMetrics;
+ if (fontType == (Font.BOLD + Font.ITALIC)) return myBoldItalicFontMetrics;
+
+ LOG.assertTrue(false);
+
+ return null;
+ }
+
+ private int getCharHeight() {
+ if(myCharHeight == -1) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
+ myCharHeight = fontMetrics.charWidth('a');
+ }
+ return myCharHeight;
+ }
+
+ public Dimension getPreferredSize() {
+ if(mySize == null) validateSize();
+ return mySize;
+ }
+
+ public JScrollPane getScrollPane() {
+ return myScrollPane;
+ }
+
+ private void recalcSize() {
+ recalcSizeInRange(0, getDocument().getTextLength(), 0, 0);
+
+ myContentSize = new Dimension(Math.max(0, myLongestLineLength), myLongestLine * getLineHeight());
+ }
+
+ private void recalcSizeInRange(int offset, int end, int curMaxLineWidth, int startLine) {
+ int x = 0;
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ IterationState state = new IterationState(this, offset, false);
+ FontMetrics fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+
+ while (offset < end) {
+ char c = text[offset];
+ if (x > curMaxLineWidth) {
+ curMaxLineWidth = x;
+ myLongestLine = startLine;
+ myLongestLineLength = x;
+ }
+
+ if (offset >= state.getEndOffset()) {
+ state.advance();
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ }
+
+ FoldRegion collapsed = state.getCurrentFold();
+ if (collapsed != null) {
+ String placeholder = collapsed.getPlaceholderText();
+ for (int i = 0; i < placeholder.length(); i++) {
+ x += fontMetrics.charWidth(placeholder.charAt(i));
+ }
+ offset = collapsed.getEndOffset();
+ } else if (c == '\t') {
+ x = nextTabStop(x);
+ offset++;
+ } else if (c == '\n') {
+ x = 0;
+ offset++;
+ startLine++;
+ } else {
+ x += fontMetrics.charWidth(c);
+ offset++;
+ }
+ }
+ }
+
+ public int logicalPositionToOffset(LogicalPosition pos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if(myDocument.getLineCount() == 0) return 0;
+
+ if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
+ if(pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
+
+ if(pos.line >= myDocument.getLineCount()) {
+ return myDocument.getTextLength();
+ }
+
+ int start = myDocument.getLineStartOffset(pos.line);
+ int end = myDocument.getLineEndOffset(pos.line);
+
+ char[] text = myDocument.getCharsNoThreadCheck();
+
+ if (pos.column == 0) return start;
+ return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
+ }
+
+ public void setLastColumnNumber(int val) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myLastColumnNumber = val;
+ }
+
+ public int getLastColumnNumber() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myLastColumnNumber;
+ }
+
+ int getVisibleLineCount() {
+ int line = getDocument().getLineCount();
+ line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
+ return line;
+ }
+
+ public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
+
+ int offset = logicalPositionToOffset(logicalPos);
+
+ FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
+ if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
+ if (offset < getDocument().getTextLength() - 1) {
+ offset = outermostCollapsed.getStartOffset();
+ LogicalPosition foldStart = offsetToLogicalPosition(offset);
+ return logicalToVisualPosition(foldStart);
+ } else {
+ offset = outermostCollapsed.getEndOffset() + 3;
+ }
+ }
+
+ int line = logicalPos.line;
+ int column = logicalPos.column;
+
+ line -= myFoldingModel.getFoldedLinesCountBefore(offset);
+ FoldRegion lastBefore = myFoldingModel.getLastCollapsedBefore(offset);
+
+ if (lastBefore != null && lastBefore.getDocument().getLineNumber(lastBefore.getEndOffset()) == logicalPos.line &&
+ lastBefore.getEndOffset() - 1 < offset) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastBefore.getStartOffset());
+ LogicalPosition foldEnd = offsetToLogicalPosition(lastBefore.getEndOffset() - 1);
+ column += foldStart.column + lastBefore.getPlaceholderText().length() - foldEnd.column - 1;
+ }
+
+
+ LOG.assertTrue(line >= 0);
+
+ return new VisualPosition(line, Math.max(0, column));
+ }
+
+ private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
+ FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
+
+ if (topLevelCollapsed == null) return null;
+
+ int start = 0;
+ int end = topLevelCollapsed.length - 1;
+ int i = 0;
+
+ while (start <= end) {
+ i = (start + end) / 2;
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line < visual.line) {
+ start = i + 1;
+ } else if (visFoldEnd.line > visual.line) {
+ end = i - 1;
+ } else if (visFoldEnd.column < visual.column) {
+ start = i + 1;
+ } else if (visFoldEnd.column > visual.column) {
+ end = i - 1;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ while (i >= 0 && i < topLevelCollapsed.length) {
+ if (topLevelCollapsed[i].isValid()) break;
+ i--;
+ }
+
+ if (i >= 0 && i < topLevelCollapsed.length) {
+ FoldRegion region = topLevelCollapsed[i];
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+ if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
+ i--;
+ if (i >= 0) {
+ return topLevelCollapsed[i];
+ } else {
+ return null;
+ }
+ }
+ return region;
+ }
+
+ return null;
+ }
+
+ public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
+
+ int line = visiblePos.line;
+ int column = visiblePos.column;
+
+ FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
+
+ if (lastCollapsedBefore != null) {
+ LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset() - 1);
+ VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
+
+ line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
+ if (visFoldEnd.line == visiblePos.line && visiblePos.column > visFoldEnd.column) {
+ LogicalPosition foldStart = offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
+ column -= foldStart.column + lastCollapsedBefore.getPlaceholderText().length() - logFoldEnd.column - 1;
+ }
+ }
+
+ if (column < 0) column = 0;
+
+ int offset = logicalPositionToOffset(new LogicalPosition(line, column));
+ FoldRegion collapsedAt = myFoldingModel.fetchOutermost(offset);
+ if (collapsedAt != null) {
+ return offsetToLogicalPosition(collapsedAt.getStartOffset());
+ }
+
+ return new LogicalPosition(line, column);
+ }
+
+ private int calcLogicalLineNumber(int offset) {
+ int textLength = myDocument.getTextLength();
+ if(textLength == 0) return 0;
+
+ if (offset > textLength || offset < 0) throw new IndexOutOfBoundsException(
+ "Wrong offset: " + offset + " textLength: " + textLength
+ );
+
+ int lineIndex = myDocument.getLineNumber(offset);
+
+ LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
+
+ return lineIndex;
+ }
+
+ private int calcColumnNumber(int offset, int lineIndex) {
+ if(myDocument.getTextLength() == 0) return 0;
+
+ char[] text = myDocument.getChars();
+ int start = myDocument.getLineStartOffset(lineIndex);
+ if (start == offset) return 0;
+ return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
+ }
+
+ private void moveCaretToScreenPos(int x, int y) {
+ if(x < 0) {
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
+
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ if(lineNumber >= myDocument.getLineCount()) {
+ lineNumber = myDocument.getLineCount() - 1;
+ }
+ if(!mySettings.isVirtualSpace()) {
+ if(lineNumber >= 0) {
+ int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
+ int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
+ if(columnNumber > lineEndColumnNumber) {
+ columnNumber = lineEndColumnNumber;
+ }
+ }
+ }
+ if(lineNumber < 0) {
+ lineNumber = 0;
+ columnNumber = 0;
+ }
+ if(!mySettings.isCaretInsideTabs()) {
+ int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
+ char[] text = myDocument.getChars();
+ if(offset >= 0 && offset < myDocument.getTextLength()) {
+ if(text[offset] == '\t') {
+ columnNumber = calcColumnNumber(offset, lineNumber);
+ }
+ }
+ }
+ LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos1);
+ }
+
+ private void runMousePressedCommand(final MouseEvent e) {
+ myMousePressedEvent = e;
+ EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
+
+ EditorMouseListener[] mouseListeners = (EditorMouseListener[])myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
+ for(int i=0; i 1000) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ }
+
+ int x = e.getX();
+ int y = e.getY();
+
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+
+ if (getMouseEventArea(e) == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
+ final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
+ if (range != null) {
+ final boolean expansion = !range.isExpanded();
+
+ int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
+ Runnable processor = new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ range.setExpanded(expansion);
+ }
+ };
+ getFoldingModel().runBatchFoldingOperation(processor);
+ y = myGutterComponent.getHeadCenterY(range);
+ getScrollingModel().scrollVertically(y - scrollShift);
+ return;
+ }
+ }
+
+ if (e.getSource() == myGutterComponent) {
+ if (getMouseEventArea(e) == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mousePressed(e);
+ if (e.isConsumed()) return;
+ }
+ x = 0;
+ }
+
+ LogicalPosition pos = xyToLogicalPosition(new Point(e.getX(), e.getY()));
+ int columnNumber = pos.column;
+ int lineNumber = pos.line;
+
+ LogicalPosition oldCaret = getCaretModel().getLogicalPosition();
+ int oldCaretOffset = getCaretModel().getOffset();
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ moveCaretToScreenPos(x, y);
+
+ if (e.isPopupTrigger()) return;
+
+ int caretOffset = getCaretModel().getOffset();
+
+ myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
+ myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
+ caretOffset >= mySelectionModel.getSelectionStart() &&
+ caretOffset <= mySelectionModel.getSelectionEnd() &&
+ caretOffset != oldCaretOffset;
+
+ if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
+ int[] starts = mySelectionModel.getBlockSelectionStarts();
+ int[] ends = mySelectionModel.getBlockSelectionEnds();
+ for (int i = 0; i < starts.length; i++) {
+ if (caretOffset >= starts[i] && caretOffset < ends[i]) {
+ myMousePressedInsideSelection = true;
+ break;
+ }
+ }
+ }
+
+ if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretOffset < mySavedSelectionStart) {
+ mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
+ } else {
+ mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
+ }
+ } else {
+ mySelectionModel.setSelection(oldSelectionStart, caretOffset);
+ }
+ } else if (columnNumber != oldCaret.column || lineNumber != oldCaret.line) {
+ setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
+ if (!myMousePressedInsideSelection) {
+ mySelectionModel.setSelection(caretOffset, caretOffset);
+ }
+ } else if (!e.isPopupTrigger()) {
+ switch(e.getClickCount()) {
+ case 2:
+ mySelectionModel.selectWordAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+
+ case 3:
+
+ mySelectionModel.selectLineAtCaret();
+ setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
+ mySavedSelectionStart = mySelectionModel.getSelectionStart();
+ mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
+ break;
+ }
+ }
+
+ requestFocus();
+ }
+
+ private static boolean isControlKeyDown(MouseEvent mouseEvent) {
+ return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
+ }
+
+ private void processMouseReleased(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ myGutterComponent.mouseReleased(e);
+ }
+
+ if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
+ return;
+ }
+
+ if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
+ final FoldRegion region = ((FoldingModelEx) getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
+ if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
+ getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ public void run() {
+ myFoldingModel.flushCaretShift();
+ region.setExpanded(true);
+ }
+ });
+ }
+
+ }
+
+ DataContext getDataContext() {
+ return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
+ }
+
+ public DataContext getProjectAwareDataContext(final DataContext original) {
+ if (original.getData(DataConstants.PROJECT) == myProject) return original;
+
+ return new DataContext() {
+ public Object getData(String dataId) {
+ if (DataConstants.PROJECT.equals(dataId)) {
+ return myProject;
+ }
+ return original.getData(dataId);
+ }
+ };
+ }
+
+
+ private EditorMouseEventArea getMouseEventArea(MouseEvent e) {
+ if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
+
+ int x = e.getX();
+
+ if (x >= myGutterComponent.getLineNumberAreaOffset() &&
+ x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth())
+ return EditorMouseEventArea.LINE_NUMBERS_AREA;
+
+ if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
+ x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth())
+ return EditorMouseEventArea.LINE_MARKERS_AREA;
+
+ if (x >= myGutterComponent.getFoldingAreaOffset() &&
+ x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth())
+ return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
+
+ return null;
+ }
+
+ private void requestFocus() {
+ myEditorComponent.requestFocus();
+ }
+
+ private void validateMousePointer(MouseEvent e) {
+ if (e.getSource() == myGutterComponent) {
+ FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
+ myGutterComponent.setActiveFoldRegion(foldingAtCursor);
+ if (foldingAtCursor != null) {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ } else {
+ myGutterComponent.setActiveFoldRegion(null);
+ if (getSelectionModel().hasSelection() &&
+ (e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
+ int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
+ if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+ }
+ myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+ }
+ }
+
+ private void runMouseDraggedCommand(final MouseEvent e) {
+ if(myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
+ return;
+ }
+ myCommandProcessor.executeCommand(myProject, new Runnable() {
+ public void run() {
+ processMouseDragged(e);
+ }
+ }, "", MOUSE_DRAGGED_GROUP);
+ }
+
+ private void processMouseDragged(MouseEvent e) {
+ if (SwingUtilities.isRightMouseButton(e)) {
+ return;
+ }
+ Rectangle rect = getScrollingModel().getVisibleArea();
+
+ int dx = 0;
+ int x = e.getX();
+
+ if (e.getSource() == myGutterComponent) {
+ x = 0;
+ }
+
+ if (x < rect.x) {
+ dx = x - rect.x;
+ } else if (x > rect.x + rect.width) {
+ dx = x - rect.x - rect.width;
+ }
+
+ int dy = 0;
+ int y = e.getY();
+ if (y < rect.y) {
+ dy = y - rect.y;
+ } else if (y > rect.y + rect.height) {
+ dy = y - rect.y - rect.height;
+ }
+ if (dx == 0 && dy == 0) {
+ myScrollingTimer.stop();
+
+ SelectionModelEx selectionModel = (SelectionModelEx) getSelectionModel();
+ int oldSelectionStart = selectionModel.getLeadSelectionOffset();
+ int oldCaretOffset = getCaretModel().getOffset();
+ LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
+ moveCaretToScreenPos(x, y);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (myMousePressedEvent != null &&
+ getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA) {
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else {
+ if (e.isAltDown()) {
+ final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret;
+ selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
+ } else if (!myMousePressedInsideSelection) {
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ selectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ selectionModel.setSelection(oldSelectionStart, newCaretOffset);
+ } else if (caretShift != 0) {
+ if (myMousePressedEvent != null) {
+ boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
+ mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
+ getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
+ isCopy ? TransferHandler.COPY : TransferHandler.MOVE
+ );
+ myMousePressedEvent = null;
+ }
+ }
+ }
+ } else {
+ myScrollingTimer.start(dx, dy);
+ }
+ }
+
+ private static class RepaintCursorThread extends Thread {
+ private long mySleepTime = 500;
+ private boolean myIsBlinkCaret = true;
+ private EditorImpl myEditor = null;
+ private boolean isStopped = false;
+ private MyRepaintRunnable myRepaintRunnable;
+
+ public RepaintCursorThread() {
+ super("EditorCaretThread");
+ myRepaintRunnable = new MyRepaintRunnable();
+ }
+
+ private class MyRepaintRunnable implements Runnable {
+ public void run() {
+ if (myEditor != null) {
+ myEditor.myCaretCursor.repaint();
+ }
+ }
+ }
+
+ public void setBlinkPeriod(int blinkPeriod) {
+ mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
+ }
+
+ public void setBlinkCaret(boolean value) {
+ myIsBlinkCaret = value;
+ }
+
+ public synchronized void stopThread() {
+ isStopped = true;
+ }
+
+ public void run() {
+ while(true) {
+ try {
+ Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
+ }
+ catch(InterruptedException e) {
+ }
+
+ synchronized(this) {
+ if(isStopped) {
+ break;
+ }
+ }
+
+ if(myEditor == null) {
+ continue;
+ }
+ CaretCursor activeCursor = myEditor.myCaretCursor;
+
+ long time = System.currentTimeMillis();
+ time -= activeCursor.myStartTime;
+
+ if(time > mySleepTime) {
+ boolean toRepaint = true;
+ if(myIsBlinkCaret) {
+ activeCursor.isVisible = !activeCursor.isVisible;
+ }
+ else {
+
+ toRepaint = !activeCursor.isVisible;
+ activeCursor.isVisible = true;
+ }
+
+ if(toRepaint) {
+ SwingUtilities.invokeLater(myRepaintRunnable);
+ }
+ }
+ }
+ }
+ }
+
+ void updateCaretCursor() {
+ if (!IJSwingUtilities.hasFocus(getContentComponent())) {
+ stopOptimizedScrolling();
+ }
+
+ if (myCursorUpdater == null) {
+ myCursorUpdater = new Runnable() {
+ public void run() {
+ myCursorUpdater = null;
+ validateSize();
+ char[] text = myDocument.getCharsNoThreadCheck();
+ int offset = getCaretModel().getOffset();
+ char c = offset >= myDocument.getTextLength() ? ' ' : text[offset];
+ if (c == '\t' || c == '\n') c = ' ';
+
+ FontMetrics fontMetrics;
+
+ if (offset < myDocument.getTextLength()) {
+ IterationState state = new IterationState(EditorImpl.this, offset, false);
+ fontMetrics = getFontMetrics(state.getMergedAttributes().getFontType());
+ } else {
+ fontMetrics = getFontMetrics(Font.PLAIN);
+ }
+
+ int width = fontMetrics.charWidth(c);
+ myCaretCursor.setPosition(visualPositionToXY(getCaretModel().getVisualPosition()), width);
+ }
+ };
+
+ SwingUtilities.invokeLater(myCursorUpdater);
+ }
+ }
+
+ public void setCaretVisible(boolean b) {
+ if (b) {
+ myCaretCursor.activate();
+ } else {
+ myCaretCursor.passivate();
+ }
+ }
+
+ public void addFocusListener(FocusChangeListener listener) {
+ myFocusListeners.add(listener);
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public boolean isOneLineMode() {
+ return myIsOneLineMode;
+ }
+
+ public void setOneLineMode(boolean isOneLineMode) {
+ myIsOneLineMode = isOneLineMode;
+ }
+
+ public void stopOptimizedScrolling() {
+ myEditorComponent.setOpaque(false);
+ }
+
+ private class CaretCursor {
+ private Point myLocation;
+ private int myWidth;
+ private boolean isVisible = true;
+ private long myStartTime = 0;
+
+ public CaretCursor() {
+ myLocation = new Point(0, 0);
+ }
+
+ public void activate() {
+ synchronized (ourCaretThread) {
+ ourCaretThread.myEditor = EditorImpl.this;
+ ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
+ ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
+ isVisible = true;
+ }
+ }
+
+ public void passivate() {
+ synchronized(ourCaretThread) {
+ isVisible = false;
+ }
+ }
+
+ public void setPosition(Point location, int width) {
+ myStartTime = System.currentTimeMillis();
+ myLocation = location;
+ isVisible = true;
+ myWidth = Math.max(width, 2);
+ repaint();
+ }
+
+ private void repaint() {
+ EditorImpl.this.myEditorComponent.repaintEditorComponent(
+ myLocation.x,
+ myLocation.y,
+ myWidth,
+ getLineHeight()
+ );
+ }
+
+ public void paint(Graphics g) {
+ if(!isVisible || !IJSwingUtilities.hasFocus(getContentComponent())) return;
+
+ int x = myLocation.x;
+ int lineHeight = getLineHeight();
+ int y = myLocation.y;
+
+ Rectangle viewRect = getScrollingModel().getVisibleArea();
+ if(x - viewRect.x < 0) {
+ return;
+ }
+
+
+ g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
+ if(!SystemInfo.isMac) {
+ Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
+ g.setXORMode(background != null ? background : Color.white);
+ }
+
+ if (EditorImpl.this.isInsertMode != mySettings.isBlockCursor()){
+ for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
+ g.drawLine(x + i, y, x + i, y + lineHeight - 1);
+ }
+ }
+ else{
+ g.fillRect(x, y, myWidth, lineHeight - 1);
+ }
+
+ g.setPaintMode();
+ }
+ }
+
+ private class ScrollingTimer {
+ Timer myTimer;
+ private static final int TIMER_PERIOD = 100;
+ private static final int CYCLE_SIZE = 20;
+ private int myXCycles ;
+ private int myYCycles;
+ private int myDx;
+ private int myDy;
+ private int xPassedCycles = 0;
+ private int yPassedCycles = 0;
+ public void start(int dx, int dy) {
+ myDx = 0;
+ myDy = 0;
+ if(dx > 0) {
+ myXCycles = CYCLE_SIZE/dx+1;
+ myDx = 1+dx/CYCLE_SIZE;
+ }
+ else if(dx < 0) {
+ myXCycles = -CYCLE_SIZE/dx+1;
+ myDx = -1+dx/CYCLE_SIZE;
+ }
+
+ if(dy > 0) {
+ myYCycles = CYCLE_SIZE/dy+1;
+ myDy = 1+dy/CYCLE_SIZE;
+ }
+ else if(dy < 0) {
+ myYCycles = -CYCLE_SIZE/dy+1;
+ myDy = -1+dy/CYCLE_SIZE;
+ }
+
+ if(myTimer != null) {
+ return;
+ }
+ myTimer = new Timer(TIMER_PERIOD,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myCommandProcessor.executeCommand(
+ myProject, new Runnable() {
+ public void run() {
+ int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
+ LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
+ int columnNumber = caretPosition.column;
+ xPassedCycles++;
+ if(xPassedCycles >= myXCycles) {
+ xPassedCycles = 0;
+ columnNumber += myDx;
+ }
+
+ int lineNumber = caretPosition.line;
+ yPassedCycles++;
+ if(yPassedCycles >= myYCycles) {
+ yPassedCycles = 0;
+ lineNumber += myDy;
+ }
+
+ LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
+ getCaretModel().moveToLogicalPosition(pos);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+
+ int newCaretOffset = getCaretModel().getOffset();
+ int caretShift = newCaretOffset - mySavedSelectionStart;
+
+ if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
+ if (caretShift < 0) {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretStart();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0))
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
+ } else {
+ int newSelection = newCaretOffset;
+ if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
+ newSelection = mySelectionModel.getWordAtCaretEnd();
+ } else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
+ newSelection = logicalPositionToOffset(
+ visualToLogicalPosition(
+ new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)
+ )
+ );
+ }
+ if (newSelection < 0) newSelection = newCaretOffset;
+ mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
+ }
+ return;
+ }
+
+ if (mySelectionModel.hasBlockSelection()) {
+ mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition());
+ }
+ else {
+ mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
+ }
+ }
+ },
+ "Move Cursor", null
+ );
+ }
+ }
+ );
+ myTimer.start();
+ }
+
+ public void stop() {
+ if(myTimer != null) {
+ myTimer.stop();
+ myTimer = null;
+ }
+ }
+
+ }
+
+ class MyScrollBar extends JScrollBar {
+
+ public MyScrollBar(int orientation) {
+ super(orientation);
+ }
+
+ /**
+ * This is helper method. It returns height of the top (descrease) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getDecScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field decrButtonField=BasicScrollBarUI.class.getDeclaredField("decrButton");
+ decrButtonField.setAccessible(true);
+ JButton decrButtonValue=(JButton)decrButtonField.get(ui);
+ LOG.assertTrue(decrButtonValue!=null);
+ return insets.top + decrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.top+15;
+ }
+ }
+
+ /**
+ * This is helper method. It returns height of the bottom (increase) scrollbar
+ * button. Please note, that it's possible to return real height only if scrollbar
+ * is instance of BasicScrollBarUI. Otherwise it return fake (but good enough :) )
+ * value.
+ */
+ int getIncScrollButtonHeight(){
+ ScrollBarUI ui=getUI();
+ Insets insets = getInsets();
+ if(ui instanceof BasicScrollBarUI){
+ try{
+ Field incrButtonField=BasicScrollBarUI.class.getDeclaredField("incrButton");
+ incrButtonField.setAccessible(true);
+ JButton incrButtonValue=(JButton)incrButtonField.get(ui);
+ LOG.assertTrue(incrButtonValue!=null);
+ return insets.bottom + incrButtonValue.getHeight();
+ }catch(Exception exc){
+ exc.printStackTrace();
+ throw new IllegalStateException(exc.getMessage());
+ }
+ }else{
+ return insets.bottom+15;
+ }
+ }
+
+ public int getUnitIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+
+ public int getBlockIncrement(int direction) {
+ JViewport vp = myScrollPane.getViewport();
+ Rectangle vr = vp.getViewRect();
+ return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
+ }
+ }
+
+ private MyEditable getViewer() {
+ if (myEditable == null) {
+ myEditable = new MyEditable();
+ }
+ return myEditable;
+ }
+
+ public CopyProvider getCopyProvider() {
+ return getViewer();
+ }
+
+ public CutProvider getCutProvider() {
+ return getViewer();
+ }
+
+ public PasteProvider getPasteProvider() {
+
+ return getViewer();
+ }
+
+ public DeleteProvider getDeleteProvider() {
+ return getViewer();
+ }
+
+ private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
+ public void performCopy(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
+ }
+
+ public boolean isCopyEnabled(DataContext dataContext) {
+ return true;
+ }
+
+ public void performCut(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
+ }
+
+ public boolean isCutEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void performPaste(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
+ }
+
+ public boolean isPasteEnabled(DataContext dataContext) {
+ return !isViewer() && getDocument().isWritable();
+ }
+
+ public void deleteElement(DataContext dataContext) {
+ executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
+ }
+
+ public boolean canDeleteElement() {
+ return isViewer() && getDocument().isWritable();
+ }
+
+ private void executeAction(String actionId, DataContext dataContext) {
+ EditorAction action = (EditorAction) ActionManager.getInstance().getAction(actionId);
+ if (action != null) {
+ action.actionPerformed(EditorImpl.this, dataContext);
+ }
+ }
+ }
+
+ public void setColorsScheme(EditorColorsScheme scheme) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScheme = scheme;
+ reinitSettings();
+ }
+
+ public EditorColorsScheme getColorsScheme() {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ return myScheme;
+ }
+
+ public void setVerticalScrollbarOrientation(int type) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ myScrollbarOrientation = type;
+ if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
+ myScrollPane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
+ } else {
+ myScrollPane.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+ }
+ }
+
+ public void setVerticalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public void setHorizontalScrollbarVisible(boolean b) {
+ if (b) {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
+ }
+ else {
+ myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ }
+ }
+
+ public int getVerticalScrollbarOrientation() {
+ return myScrollbarOrientation;
+ }
+
+ public MyScrollBar getVerticalScrollBar() {
+ return myVerticalScrollBar;
+ }
+
+ public JPanel getPanel() {
+ return myPanel;
+ }
+
+ private int getMouseSelectionState() {
+ return myMouseSelectionState;
+ }
+
+ private void setMouseSelectionState(int mouseSelectionState) {
+ myMouseSelectionState = mouseSelectionState;
+ myMouseSelectionChangeTimestamp = System.currentTimeMillis();
+ }
+
+
+ public void replaceInputMethodText(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.replaceInputMethodText(e);
+ }
+
+ public void inputMethodCaretPositionChanged(InputMethodEvent e) {
+ getInputMethodRequests();
+ myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
+ }
+
+ public InputMethodRequests getInputMethodRequests() {
+ if (myInputMethodRequestsHandler == null) {
+ myInputMethodRequestsHandler = new MyInputMethodHandler();
+ myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
+ }
+ return myInputMethodRequestsSwingWrapper;
+ }
+
+
+ private class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
+ private InputMethodRequests myDelegate;
+
+ public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
+ myDelegate = delegate;
+ }
+
+ public Rectangle getTextLocation(final TextHitInfo offset) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getTextLocation(offset);
+
+ final Rectangle[] r = new Rectangle[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getTextLocation(offset);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public TextHitInfo getLocationOffset(final int x, final int y) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
+
+ final TextHitInfo[] r = new TextHitInfo[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getLocationOffset(x, y);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getInsertPositionOffset() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getInsertPositionOffset();
+
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getInsertPositionOffset();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
+ final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public int getCommittedTextLength() {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getCommittedTextLength();
+ final int[] r = new int[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getCommittedTextLength();
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
+ if (ApplicationManagerEx.getApplicationEx().isDispatchThread()) return myDelegate.getSelectedText(attributes);
+
+ final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ r[0] = myDelegate.getSelectedText(attributes);
+ }
+ });
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (InvocationTargetException e) {
+ LOG.error(e);
+ }
+ return r[0];
+ }
+ }
+
+ private class MyInputMethodHandler implements InputMethodRequests {
+ private String composedText;
+ private int composedTextStart;
+ private int composedTextEnd;
+
+ public Rectangle getTextLocation(TextHitInfo offset) {
+ Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
+ Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
+ Point p = getContentComponent().getLocationOnScreen();
+ r.translate(p.x, p.y);
+
+ return r;
+ }
+
+ public TextHitInfo getLocationOffset(int x, int y) {
+ if (composedText != null) {
+ Point p = getContentComponent().getLocationOnScreen();
+ p.x = x - p.x;
+ p.y = y - p.y;
+ int pos = logicalPositionToOffset(xyToLogicalPosition(p));
+ if ((pos >= composedTextStart) && (pos <= composedTextEnd)) {
+ return TextHitInfo.leading(pos - composedTextStart);
+ }
+ }
+ return null;
+ }
+
+ public int getInsertPositionOffset() {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ int caretIndex = getCaretModel().getOffset();
+
+ if (caretIndex < composedStartIndex) {
+ return caretIndex;
+ } else if (caretIndex < composedEndIndex) {
+ return composedStartIndex;
+ } else {
+ return caretIndex - (composedEndIndex - composedStartIndex);
+ }
+ }
+
+ private String getText(int startIdx, int endIdx) {
+ char[] chars = getDocument().getChars();
+ return new String(chars, startIdx, endIdx - startIdx);
+ }
+
+ public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
+ AttributedCharacterIterator.Attribute[] attributes) {
+ int composedStartIndex = 0;
+ int composedEndIndex = 0;
+ if (composedText != null) {
+ composedStartIndex = composedTextStart;
+ composedEndIndex = composedTextEnd;
+ }
+
+ String committed;
+ if (beginIndex < composedStartIndex) {
+ if (endIndex <= composedStartIndex) {
+ committed = getText(beginIndex, endIndex - beginIndex);
+ }
+ else {
+ int firstPartLength = composedStartIndex - beginIndex;
+ committed = getText(beginIndex, firstPartLength) +
+ getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
+ }
+ }
+ else {
+ committed = getText(
+ beginIndex + (composedEndIndex - composedStartIndex),
+ endIndex - beginIndex
+ );
+ }
+
+ return new AttributedString(committed).getIterator();
+ }
+
+ public int getCommittedTextLength() {
+ int length = getDocument().getTextLength();
+ if (composedText != null) {
+ length -= composedText.length();
+ }
+ return length;
+ }
+
+ public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
+ return null;
+ }
+
+ public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
+ String text = getSelectionModel().getSelectedText();
+ return text == null ? null : new AttributedString(text).getIterator();
+ }
+
+ private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
+ StringBuffer strBuf = new StringBuffer();
+
+ // create attributed string with no attributes
+ for (char c = text.setIndex(composedIndex);
+ c != CharacterIterator.DONE; c = text.next()) {
+ strBuf.append(c);
+ }
+
+ composedText = new String(strBuf);
+ }
+ private void setInputMethodCaretPosition(InputMethodEvent e) {
+ if (composedText != null) {
+ int dot;
+ dot = composedTextStart;
+
+ TextHitInfo caretPos = e.getCaret();
+ if (caretPos != null) {
+ int index = caretPos.getInsertionIndex();
+ dot += index;
+ }
+
+ getCaretModel().moveToOffset(dot);
+ getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+ }
+ }
+
+ private void runUndoTransparent(final Runnable runnable) {
+ UndoManager undoManager = myProject == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(myProject);
+ undoManager.runUndoTransparentAction(new Runnable() {
+ public void run() {
+ CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(runnable);
+ }
+ }, "", null);
+ }
+ });
+ }
+
+ private void replaceInputMethodText(InputMethodEvent e) {
+ int commitCount = e.getCommittedCharacterCount();
+ AttributedCharacterIterator text = e.getText();
+ int composedTextIndex;
+
+ // old composed text deletion
+ final Document doc = getDocument();
+ if (composedText != null) {
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ doc.deleteString(composedTextStart, composedTextEnd);
+ }
+ });
+ composedText = null;
+ }
+
+ if (text != null) {
+ text.first();
+
+ // committed text insertion
+ if (commitCount > 0) {
+ for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
+ if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
+ processKeyTyped(c);
+ }
+ }
+ }
+
+ // new composed text insertion
+ composedTextIndex = text.getIndex();
+ if (composedTextIndex < text.getEndIndex()) {
+ createComposedString(composedTextIndex, text);
+
+ runUndoTransparent(new Runnable() {
+ public void run() {
+ EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
+ }
+ });
+
+ composedTextStart = getCaretModel().getOffset();
+ composedTextEnd = getCaretModel().getOffset() + composedText.length();
+ }
+ }
+ }
+ }
+
+ private class MyMouseAdapter extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ runMousePressedCommand(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ runMouseReleasedCommand(e);
+ if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
+ Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(getFontMetrics(Font.PLAIN)) &&
+ Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
+ runMouseClickedCommand(e);
+ }
+ myMousePressedEvent = null;
+ }
+
+ public void mouseEntered(MouseEvent e){
+ runMouseEnteredCommand(e);
+ }
+ public void mouseExited(MouseEvent e){
+ runMouseExitedCommand(e);
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ public void mouseDragged(MouseEvent e) {
+ validateMousePointer(e);
+ runMouseDraggedCommand(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseDragged(e);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseDragged(event);
+ }
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ validateMousePointer(e);
+ EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
+ if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
+ myGutterComponent.mouseMoved(e);
+ }
+
+ if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
+ FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
+ HintManager.getInstance().getTooltipController().showTooptipByMouseMove(EditorImpl.this, e, fold);
+ }
+
+ EditorMouseMotionListener[] listeners = (EditorMouseMotionListener[]) myMouseMotionListeners.toArray(
+ new EditorMouseMotionListener[myMouseMotionListeners.size()]
+ );
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].mouseMoved(event);
+ }
+ }
+ }
+
+ private class MyColorSchemeDelegate implements EditorColorsScheme {
+ private HashMap myOwnAttributes = new HashMap();
+ private HashMap myOwnColors = new HashMap();
+
+
+ private EditorColorsScheme getGlobal() {
+ return EditorColorsManager.getInstance().getGlobalScheme();
+ }
+
+ public String getName() {
+ return getGlobal().getName();
+ }
+
+ public void setName(String name) {
+ getGlobal().setName(name);
+ }
+
+ public TextAttributes getAttributes(String key) {
+ if (myOwnAttributes.containsKey(key)) return (TextAttributes) myOwnAttributes.get(key);
+ return getGlobal().getAttributes(key);
+ }
+
+ public void setAttributes(String key, TextAttributes attributes) {
+ myOwnAttributes.put(key, attributes);
+ }
+
+ public Color getColor(String key) {
+ if (myOwnColors.containsKey(key)) return (Color) myOwnColors.get(key);
+ return getGlobal().getColor(key);
+ }
+
+ public void setColor(String key, Color color) {
+ myOwnColors.put(key, color);
+
+ // These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
+ // settings in this case.
+ myCaretModel.reinitSettings();
+ mySelectionModel.reinitSettings();
+ }
+
+ public int getEditorFontSize() {
+ return getGlobal().getEditorFontSize();
+ }
+
+ public void setEditorFontSize(int fontSize) {
+ getGlobal().setEditorFontSize(fontSize);
+ }
+
+ public String getEditorFontName() {
+ return getGlobal().getEditorFontName();
+ }
+
+ public void setEditorFontName(String fontName) {
+ getGlobal().setEditorFontName(fontName);
+ }
+
+ public Font getFont(EditorFontType key) {
+ return getGlobal().getFont(key);
+ }
+
+ public void setFont(EditorFontType key, Font font) {
+ getGlobal().setFont(key, font);
+ }
+
+ public float getLineSpacing() {
+ return getGlobal().getLineSpacing();
+ }
+
+ public void setLineSpacing(float lineSpacing) {
+ getGlobal().setLineSpacing(lineSpacing);
+ }
+
+ public Object clone() {
+ return null;
+ }
+
+ public void readExternal(Element element) throws InvalidDataException {
+ }
+
+ public void writeExternal(Element element) throws WriteExternalException {
+ }
+ }
+
+ private static class MyTransferHandler extends TransferHandler {
+ private RangeMarker myDraggedRange = null;
+
+ private static Editor getEditor(JComponent comp) {
+ EditorComponentImpl editorComponent = (EditorComponentImpl) comp;
+ return editorComponent.getEditor();
+ }
+
+ public boolean importData(final JComponent comp, final Transferable t) {
+ final EditorImpl editor = (EditorImpl) getEditor(comp);
+
+ final int caretOffset = editor.getCaretModel().getOffset();
+ if (myDraggedRange != null &&
+ myDraggedRange.getStartOffset() <= caretOffset &&
+ caretOffset < myDraggedRange.getEndOffset()) {
+ return false;
+ }
+
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
+ }
+
+ CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ editor.getSelectionModel().removeSelection();
+ if (myDraggedRange != null) {
+ editor.getCaretModel().moveToOffset(caretOffset);
+ }
+
+ EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_PASTE);
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable backup = clipboard.getContents(this);
+ ClipboardOwner clipboardOwner = new ClipboardOwner() {
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+ };
+ clipboard.setContents(t, clipboardOwner);
+
+ editor.putUserData(LAST_PASTED_REGION, null);
+ pasteHandler.execute(editor, editor.getDataContext());
+ clipboard.setContents(backup, clipboardOwner);
+
+ TextRange range = (TextRange) editor.getUserData(LAST_PASTED_REGION);
+ if (range != null) {
+ editor.getCaretModel().moveToOffset(range.getStartOffset());
+ editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
+ }
+ } catch (Exception exception) {
+ LOG.error(exception);
+ }
+ }
+ });
+ }
+ }, "Paste", DND_COMMAND_KEY);
+
+ return true;
+ }
+
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ Editor editor = getEditor(comp);
+ if (editor.isViewer()) return false;
+ if (!editor.getDocument().isWritable()) return false;
+
+ for (int i = 0; i < transferFlavors.length; i++) {
+ DataFlavor transferFlavor = transferFlavors[i];
+ if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
+ }
+
+ return false;
+ }
+
+ protected Transferable createTransferable(JComponent c) {
+ Editor editor = getEditor(c);
+ String s = editor.getSelectionModel().getSelectedText();
+ if (s == null) return null;
+ int selectionStart = editor.getSelectionModel().getSelectionStart();
+ int selectionEnd = editor.getSelectionModel().getSelectionEnd();
+ myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
+
+ return new StringSelection(s);
+ }
+
+ public int getSourceActions(JComponent c) {
+ return COPY_OR_MOVE;
+ }
+
+ protected void exportDone(final JComponent source, Transferable data, int action) {
+ if (data == null) return;
+
+ if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
+ CommandProcessor.getInstance().executeCommand(((EditorImpl) getEditor(source)).myProject, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ getEditor(source).getDocument().deleteString(myDraggedRange.getStartOffset(),
+ myDraggedRange.getEndOffset()
+ );
+ }
+ });
+ }
+ }, "Move selection", DND_COMMAND_KEY);
+ }
+
+ myDraggedRange = null;
+ }
+ }
+
+ class EditorDocumentAdapter extends DocumentAdapter {
+ public void beforeDocumentChange(DocumentEvent e) {
+ beforeChangedUpdate(e);
+ }
+
+ public void documentChanged(DocumentEvent e) {
+ changedUpdate(e);
+ }
+ }
+}
diff --git a/platform/platform-tests/testData/diff/abc.txt b/platform/platform-tests/testData/diff/abc.txt
new file mode 100644
index 000000000000..e7156d8b223f
--- /dev/null
+++ b/platform/platform-tests/testData/diff/abc.txt
@@ -0,0 +1,15 @@
+class abc {
+ public void f() {
+<<<<<<< abc.txt
+ def
+=======
+ abc
+>>>>>>> 1.14
+ int F() {
+ asdfklasdfl
+ asdgfsagdfhasfdhg
+ sdafjklklas
+ sdafjkllksadf
+
+ }
+}
\ No newline at end of file
diff --git a/platform/platform-tests/testData/diff/applyPatch/addFile/after/1.txt b/platform/platform-tests/testData/diff/applyPatch/addFile/after/1.txt
new file mode 100644
index 000000000000..4fcefbf2acb2
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/addFile/after/1.txt
@@ -0,0 +1,3 @@
+One
+Two
+Three
diff --git a/platform/platform-tests/testData/diff/applyPatch/addFile/after/unchanged.txt b/platform/platform-tests/testData/diff/applyPatch/addFile/after/unchanged.txt
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/platform/platform-tests/testData/diff/applyPatch/addFile/apply.patch b/platform/platform-tests/testData/diff/applyPatch/addFile/apply.patch
new file mode 100644
index 000000000000..bb4c248ccdbd
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/addFile/apply.patch
@@ -0,0 +1,6 @@
+--- after/1.txt after
++++ after/1.txt after
+@@ -0,0 +1,3 @@
++One
++Two
++Three
diff --git a/platform/platform-tests/testData/diff/applyPatch/addFile/before/unchanged.txt b/platform/platform-tests/testData/diff/applyPatch/addFile/before/unchanged.txt
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/platform/platform-tests/testData/diff/applyPatch/addFileWithGitVersion/after/1.txt b/platform/platform-tests/testData/diff/applyPatch/addFileWithGitVersion/after/1.txt
new file mode 100644
index 000000000000..4fcefbf2acb2
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/addFileWithGitVersion/after/1.txt
@@ -0,0 +1,3 @@
+One
+Two
+Three
diff --git a/platform/platform-tests/testData/diff/applyPatch/addFileWithGitVersion/after/unchanged.txt b/platform/platform-tests/testData/diff/applyPatch/addFileWithGitVersion/after/unchanged.txt
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/platform/platform-tests/testData/diff/applyPatch/addFileWithGitVersion/apply.patch b/platform/platform-tests/testData/diff/applyPatch/addFileWithGitVersion/apply.patch
new file mode 100644
index 000000000000..7e2769dd483f
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/addFileWithGitVersion/apply.patch
@@ -0,0 +1,8 @@
+--- after/1.txt after
++++ after/1.txt after
+@@ -0,0 +1,3 @@
++One
++Two
++Three
+--
+1.7.9.5
\ No newline at end of file
diff --git a/platform/platform-tests/testData/diff/applyPatch/addFileWithGitVersion/before/unchanged.txt b/platform/platform-tests/testData/diff/applyPatch/addFileWithGitVersion/before/unchanged.txt
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/platform/platform-tests/testData/diff/applyPatch/addFileWithoutNewlineAtEOF/after/1.txt b/platform/platform-tests/testData/diff/applyPatch/addFileWithoutNewlineAtEOF/after/1.txt
new file mode 100644
index 000000000000..40523d5eba10
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/addFileWithoutNewlineAtEOF/after/1.txt
@@ -0,0 +1,3 @@
+One
+Two
+Three
\ No newline at end of file
diff --git a/platform/platform-tests/testData/diff/applyPatch/addFileWithoutNewlineAtEOF/after/unchanged.txt b/platform/platform-tests/testData/diff/applyPatch/addFileWithoutNewlineAtEOF/after/unchanged.txt
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/platform/platform-tests/testData/diff/applyPatch/addFileWithoutNewlineAtEOF/apply.patch b/platform/platform-tests/testData/diff/applyPatch/addFileWithoutNewlineAtEOF/apply.patch
new file mode 100644
index 000000000000..7f7814bbbf43
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/addFileWithoutNewlineAtEOF/apply.patch
@@ -0,0 +1,7 @@
+--- after/1.txt after
++++ after/1.txt after
+@@ -0,0 +1,3 @@
++One
++Two
++Three
+\ No newline at end of file
\ No newline at end of file
diff --git a/platform/platform-tests/testData/diff/applyPatch/addFileWithoutNewlineAtEOF/before/unchanged.txt b/platform/platform-tests/testData/diff/applyPatch/addFileWithoutNewlineAtEOF/before/unchanged.txt
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/platform/platform-tests/testData/diff/applyPatch/addLastLine/after/1.txt b/platform/platform-tests/testData/diff/applyPatch/addLastLine/after/1.txt
new file mode 100644
index 000000000000..64c739e31220
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/addLastLine/after/1.txt
@@ -0,0 +1,3 @@
+first
+third
+last
diff --git a/platform/platform-tests/testData/diff/applyPatch/addLastLine/apply.patch b/platform/platform-tests/testData/diff/applyPatch/addLastLine/apply.patch
new file mode 100644
index 000000000000..8ba2d13b909b
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/addLastLine/apply.patch
@@ -0,0 +1,6 @@
+--- before/1.txt before
++++ after/1.txt after
+@@ -1,2 +1,3 @@
+ first
+ third
++last
diff --git a/platform/platform-tests/testData/diff/applyPatch/addLastLine/before/1.txt b/platform/platform-tests/testData/diff/applyPatch/addLastLine/before/1.txt
new file mode 100644
index 000000000000..1effb1028069
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/addLastLine/before/1.txt
@@ -0,0 +1,2 @@
+first
+third
diff --git a/platform/platform-tests/testData/diff/applyPatch/addLine/after/1.txt b/platform/platform-tests/testData/diff/applyPatch/addLine/after/1.txt
new file mode 100644
index 000000000000..ff6e6b1a5055
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/addLine/after/1.txt
@@ -0,0 +1,3 @@
+first
+second
+third
diff --git a/platform/platform-tests/testData/diff/applyPatch/addLine/apply.patch b/platform/platform-tests/testData/diff/applyPatch/addLine/apply.patch
new file mode 100644
index 000000000000..a85b9024375a
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/addLine/apply.patch
@@ -0,0 +1,6 @@
+--- before/1.txt before
++++ after/1.txt after
+@@ -1,2 +1,3 @@
+ first
++second
+ third
diff --git a/platform/platform-tests/testData/diff/applyPatch/addLine/before/1.txt b/platform/platform-tests/testData/diff/applyPatch/addLine/before/1.txt
new file mode 100644
index 000000000000..1effb1028069
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/addLine/before/1.txt
@@ -0,0 +1,2 @@
+first
+third
diff --git a/platform/platform-tests/testData/diff/applyPatch/alreadyApplied/after/1.txt b/platform/platform-tests/testData/diff/applyPatch/alreadyApplied/after/1.txt
new file mode 100644
index 000000000000..1978663eef3b
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/alreadyApplied/after/1.txt
@@ -0,0 +1,7 @@
+uno
+dos
+cuatro
+cinco
+seis
+siete
+ocho
diff --git a/platform/platform-tests/testData/diff/applyPatch/alreadyApplied/apply.patch b/platform/platform-tests/testData/diff/applyPatch/alreadyApplied/apply.patch
new file mode 100644
index 000000000000..a735d9f809f9
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/alreadyApplied/apply.patch
@@ -0,0 +1,13 @@
+diff -r -u before/1.txt after/1.txt
+--- before/1.txt Tue Nov 21 14:20:19 2006
++++ after/1.txt Tue Nov 21 14:20:15 2006
+@@ -1,7 +1,7 @@
+ uno
+ dos
+-tres
+ cuatro
++cinco
+ seis
+ siete
+-octo
++ocho
diff --git a/platform/platform-tests/testData/diff/applyPatch/alreadyApplied/before/1.txt b/platform/platform-tests/testData/diff/applyPatch/alreadyApplied/before/1.txt
new file mode 100644
index 000000000000..1978663eef3b
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/alreadyApplied/before/1.txt
@@ -0,0 +1,7 @@
+uno
+dos
+cuatro
+cinco
+seis
+siete
+ocho
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextDiff/after/1.txt b/platform/platform-tests/testData/diff/applyPatch/contextDiff/after/1.txt
new file mode 100644
index 000000000000..81f7bed1d21f
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextDiff/after/1.txt
@@ -0,0 +1,6 @@
+one
+two
+three
+cuatro
+five
+seven
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextDiff/apply.patch b/platform/platform-tests/testData/diff/applyPatch/contextDiff/apply.patch
new file mode 100644
index 000000000000..08c21265a898
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextDiff/apply.patch
@@ -0,0 +1,18 @@
+diff -r -c before/1.txt after/1.txt
+*** before/1.txt Fri Nov 17 16:41:09 2006
+--- after/1.txt Fri Nov 17 16:40:58 2006
+***************
+*** 1,6 ****
+ one
+ three
+! four
+ five
+- six
+ seven
+--- 1,6 ----
+ one
++ two
+ three
+! cuatro
+ five
+ seven
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextDiff/before/1.txt b/platform/platform-tests/testData/diff/applyPatch/contextDiff/before/1.txt
new file mode 100644
index 000000000000..dde78455b61d
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextDiff/before/1.txt
@@ -0,0 +1,6 @@
+one
+three
+four
+five
+six
+seven
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextDiffAddLine/after/1.txt b/platform/platform-tests/testData/diff/applyPatch/contextDiffAddLine/after/1.txt
new file mode 100644
index 000000000000..d903ddf7af6e
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextDiffAddLine/after/1.txt
@@ -0,0 +1,7 @@
+first
+second
+third
+fourth
+fifth
+sixth
+seventh
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextDiffAddLine/apply.patch b/platform/platform-tests/testData/diff/applyPatch/contextDiffAddLine/apply.patch
new file mode 100644
index 000000000000..90e3a687820f
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextDiffAddLine/apply.patch
@@ -0,0 +1,13 @@
+diff -r -c before/1.txt after/1.txt
+*** before/1.txt Fri Nov 17 15:30:30 2006
+--- after/1.txt Fri Nov 17 15:30:38 2006
+***************
+*** 1,6 ****
+--- 1,7 ----
+ first
+ second
+ third
++ fourth
+ fifth
+ sixth
+ seventh
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextDiffAddLine/before/1.txt b/platform/platform-tests/testData/diff/applyPatch/contextDiffAddLine/before/1.txt
new file mode 100644
index 000000000000..2b1776423334
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextDiffAddLine/before/1.txt
@@ -0,0 +1,6 @@
+first
+second
+third
+fifth
+sixth
+seventh
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextDiffMultiFile/after/1.txt b/platform/platform-tests/testData/diff/applyPatch/contextDiffMultiFile/after/1.txt
new file mode 100644
index 000000000000..ff6e6b1a5055
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextDiffMultiFile/after/1.txt
@@ -0,0 +1,3 @@
+first
+second
+third
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextDiffMultiFile/after/2.txt b/platform/platform-tests/testData/diff/applyPatch/contextDiffMultiFile/after/2.txt
new file mode 100644
index 000000000000..15bf6080d09b
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextDiffMultiFile/after/2.txt
@@ -0,0 +1,3 @@
+uno
+dos
+tres
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextDiffMultiFile/apply.patch b/platform/platform-tests/testData/diff/applyPatch/contextDiffMultiFile/apply.patch
new file mode 100644
index 000000000000..c3b0c4d8914a
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextDiffMultiFile/apply.patch
@@ -0,0 +1,19 @@
+diff -r -c before/1.txt after/1.txt
+*** before/1.txt Tue Nov 07 13:32:58 2006
+--- after/1.txt Tue Nov 07 13:32:58 2006
+***************
+*** 1,2 ****
+--- 1,3 ----
+ first
++ second
+ third
+diff -r -c before/2.txt after/2.txt
+*** before/2.txt Fri Nov 17 18:26:49 2006
+--- after/2.txt Fri Nov 17 18:26:38 2006
+***************
+*** 1,4 ****
+ uno
+ dos
+ tres
+- cuatro
+--- 1,3 ----
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextDiffMultiFile/before/1.txt b/platform/platform-tests/testData/diff/applyPatch/contextDiffMultiFile/before/1.txt
new file mode 100644
index 000000000000..1effb1028069
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextDiffMultiFile/before/1.txt
@@ -0,0 +1,2 @@
+first
+third
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextDiffMultiFile/before/2.txt b/platform/platform-tests/testData/diff/applyPatch/contextDiffMultiFile/before/2.txt
new file mode 100644
index 000000000000..8c857923b895
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextDiffMultiFile/before/2.txt
@@ -0,0 +1,4 @@
+uno
+dos
+tres
+cuatro
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextDiffRemoveLine/after/1.txt b/platform/platform-tests/testData/diff/applyPatch/contextDiffRemoveLine/after/1.txt
new file mode 100644
index 000000000000..2b1776423334
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextDiffRemoveLine/after/1.txt
@@ -0,0 +1,6 @@
+first
+second
+third
+fifth
+sixth
+seventh
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextDiffRemoveLine/apply.patch b/platform/platform-tests/testData/diff/applyPatch/contextDiffRemoveLine/apply.patch
new file mode 100644
index 000000000000..eb94f6833630
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextDiffRemoveLine/apply.patch
@@ -0,0 +1,13 @@
+diff -r -c before/1.txt after/1.txt
+*** before/1.txt Fri Nov 17 16:29:29 2006
+--- after/1.txt Fri Nov 17 16:29:26 2006
+***************
+*** 1,7 ****
+ first
+ second
+ third
+- fourth
+ fifth
+ sixth
+ seventh
+--- 1,6 ----
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextDiffRemoveLine/before/1.txt b/platform/platform-tests/testData/diff/applyPatch/contextDiffRemoveLine/before/1.txt
new file mode 100644
index 000000000000..d903ddf7af6e
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextDiffRemoveLine/before/1.txt
@@ -0,0 +1,7 @@
+first
+second
+third
+fourth
+fifth
+sixth
+seventh
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextDiffSingleSpace/after/1.txt b/platform/platform-tests/testData/diff/applyPatch/contextDiffSingleSpace/after/1.txt
new file mode 100644
index 000000000000..d549733a1b50
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextDiffSingleSpace/after/1.txt
@@ -0,0 +1,11 @@
+uno
+
+
+dos
+tres
+
+
+cuatro
+
+
+cinco
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextDiffSingleSpace/apply.patch b/platform/platform-tests/testData/diff/applyPatch/contextDiffSingleSpace/apply.patch
new file mode 100644
index 000000000000..135da5647e61
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextDiffSingleSpace/apply.patch
@@ -0,0 +1,13 @@
+diff -r -c before/1.txt after/1.txt
+*** before/1.txt Tue Nov 21 18:10:18 2006
+--- after/1.txt Tue Nov 21 18:10:26 2006
+***************
+*** 2,7 ****
+--- 2,8 ----
+
+
+ dos
++ tres
+
+
+ cuatro
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextDiffSingleSpace/before/1.txt b/platform/platform-tests/testData/diff/applyPatch/contextDiffSingleSpace/before/1.txt
new file mode 100644
index 000000000000..b3a6b02feeb2
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextDiffSingleSpace/before/1.txt
@@ -0,0 +1,10 @@
+uno
+
+
+dos
+
+
+cuatro
+
+
+cinco
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextNoNewlineAtEof/after/1.txt b/platform/platform-tests/testData/diff/applyPatch/contextNoNewlineAtEof/after/1.txt
new file mode 100644
index 000000000000..3664b17e2df8
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextNoNewlineAtEof/after/1.txt
@@ -0,0 +1,3 @@
+uno
+dos
+tres
\ No newline at end of file
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextNoNewlineAtEof/apply.patch b/platform/platform-tests/testData/diff/applyPatch/contextNoNewlineAtEof/apply.patch
new file mode 100644
index 000000000000..6889e408241c
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextNoNewlineAtEof/apply.patch
@@ -0,0 +1,13 @@
+diff -r -c before/1.txt after/1.txt
+*** before/1.txt Fri Nov 24 13:06:16 2006
+--- after/1.txt Fri Nov 24 13:06:20 2006
+***************
+*** 1,2 ****
+ uno
+! dos
+\ No newline at end of file
+--- 1,3 ----
+ uno
+! dos
+! tres
+\ No newline at end of file
diff --git a/platform/platform-tests/testData/diff/applyPatch/contextNoNewlineAtEof/before/1.txt b/platform/platform-tests/testData/diff/applyPatch/contextNoNewlineAtEof/before/1.txt
new file mode 100644
index 000000000000..8306d505b4d9
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/contextNoNewlineAtEof/before/1.txt
@@ -0,0 +1,2 @@
+uno
+dos
\ No newline at end of file
diff --git a/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithLineBreak/after/1.txt b/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithLineBreak/after/1.txt
new file mode 100644
index 000000000000..24d6b3a04df7
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithLineBreak/after/1.txt
@@ -0,0 +1,15 @@
+import java.util.function.Supplier;
+class Test {
+
+ private void a()
+ {
+ b(newMethod());
+ }
+
+ private Supplier newMethod() {
+ return (s) -> {
+ System.out.println(s);
+ };
+ }
+
+ void b(Supplier s) {}
\ No newline at end of file
diff --git a/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithLineBreak/apply.patch b/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithLineBreak/apply.patch
new file mode 100644
index 000000000000..dacecb95b351
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithLineBreak/apply.patch
@@ -0,0 +1,13 @@
+diff --git a/before/1.txt b/after/1.txt
+index fad3998..5880be4 100644
+--- a/before/1.txt
++++ b/after/1.txt
+@@ -12,5 +12,4 @@ class Test {
+ };
+ }
+
+- void b(Supplier s) {}
+-}
+\ No newline at end of file
++ void b(Supplier s) {}
+\ No newline at end of file
diff --git a/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithLineBreak/before/1.txt b/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithLineBreak/before/1.txt
new file mode 100644
index 000000000000..fad399888133
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithLineBreak/before/1.txt
@@ -0,0 +1,16 @@
+import java.util.function.Supplier;
+class Test {
+
+ private void a()
+ {
+ b(newMethod());
+ }
+
+ private Supplier newMethod() {
+ return (s) -> {
+ System.out.println(s);
+ };
+ }
+
+ void b(Supplier s) {}
+}
\ No newline at end of file
diff --git a/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithoutLineBreak/after/1.txt b/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithoutLineBreak/after/1.txt
new file mode 100644
index 000000000000..bf3ee82452a0
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithoutLineBreak/after/1.txt
@@ -0,0 +1,15 @@
+import java.util.function.Supplier;
+class Test {
+
+ private void a()
+ {
+ b(newMethod());
+ }
+
+ private Supplier newMethod() {
+ return (s) -> {
+ System.out.println(s);
+ };
+ }
+
+ void b(Supplier s) {}
diff --git a/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithoutLineBreak/apply.patch b/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithoutLineBreak/apply.patch
new file mode 100644
index 000000000000..6c5569b931f1
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithoutLineBreak/apply.patch
@@ -0,0 +1,10 @@
+diff --git a/before/1.txt b/after/1.txt
+index fad3998..5880be4 100644
+--- a/before/1.txt
++++ b/after/1.txt
+@@ -13,4 +13,3 @@
+ }
+
+ void b(Supplier s) {}
+-}
+\ No newline at end of file
diff --git a/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithoutLineBreak/before/1.txt b/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithoutLineBreak/before/1.txt
new file mode 100644
index 000000000000..fad399888133
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/deleteLastLineWithoutLineBreak/before/1.txt
@@ -0,0 +1,16 @@
+import java.util.function.Supplier;
+class Test {
+
+ private void a()
+ {
+ b(newMethod());
+ }
+
+ private Supplier newMethod() {
+ return (s) -> {
+ System.out.println(s);
+ };
+ }
+
+ void b(Supplier s) {}
+}
\ No newline at end of file
diff --git a/platform/platform-tests/testData/diff/applyPatch/deleteLineContentWithoutLineBreak/after/1.txt b/platform/platform-tests/testData/diff/applyPatch/deleteLineContentWithoutLineBreak/after/1.txt
new file mode 100644
index 000000000000..1b08699f2ea8
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/deleteLineContentWithoutLineBreak/after/1.txt
@@ -0,0 +1,16 @@
+import java.util.function.Supplier;
+class Test {
+
+ private void a()
+ {
+ b(newMethod());
+ }
+
+ private Supplier newMethod() {
+ return (s) -> {
+ System.out.println(s);
+ };
+ }
+
+
+}
\ No newline at end of file
diff --git a/platform/platform-tests/testData/diff/applyPatch/deleteLineContentWithoutLineBreak/apply.patch b/platform/platform-tests/testData/diff/applyPatch/deleteLineContentWithoutLineBreak/apply.patch
new file mode 100644
index 000000000000..399055363976
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/deleteLineContentWithoutLineBreak/apply.patch
@@ -0,0 +1,12 @@
+diff --git a/before/1.txt b/after/1.txt
+index fad3998..5880be4 100644
+--- a/before/1.txt
++++ b/after/1.txt
+@@ -12,5 +12,5 @@
+ };
+ }
+
+- void b(Supplier s) {}
++
+ }
+\ No newline at end of file
diff --git a/platform/platform-tests/testData/diff/applyPatch/deleteLineContentWithoutLineBreak/before/1.txt b/platform/platform-tests/testData/diff/applyPatch/deleteLineContentWithoutLineBreak/before/1.txt
new file mode 100644
index 000000000000..fad399888133
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/deleteLineContentWithoutLineBreak/before/1.txt
@@ -0,0 +1,16 @@
+import java.util.function.Supplier;
+class Test {
+
+ private void a()
+ {
+ b(newMethod());
+ }
+
+ private Supplier newMethod() {
+ return (s) -> {
+ System.out.println(s);
+ };
+ }
+
+ void b(Supplier s) {}
+}
\ No newline at end of file
diff --git a/platform/platform-tests/testData/diff/applyPatch/emptyLine/after/1.txt b/platform/platform-tests/testData/diff/applyPatch/emptyLine/after/1.txt
new file mode 100644
index 000000000000..8d69aa718185
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/emptyLine/after/1.txt
@@ -0,0 +1,9 @@
+uno
+
+dos
+tres
+
+cuarto
+
+cinco
+seis
diff --git a/platform/platform-tests/testData/diff/applyPatch/emptyLine/apply.patch b/platform/platform-tests/testData/diff/applyPatch/emptyLine/apply.patch
new file mode 100644
index 000000000000..55b52234186f
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/emptyLine/apply.patch
@@ -0,0 +1,11 @@
+diff -r -u before/1.txt after/1.txt
+--- before/1.txt Mon Nov 20 14:37:58 2006
++++ after/1.txt Mon Nov 20 14:38:07 2006
+@@ -3,6 +3,7 @@
+ dos
+ tres
+
++cuarto
+
+ cinco
+ seis
diff --git a/platform/platform-tests/testData/diff/applyPatch/emptyLine/before/1.txt b/platform/platform-tests/testData/diff/applyPatch/emptyLine/before/1.txt
new file mode 100644
index 000000000000..77808628e231
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/emptyLine/before/1.txt
@@ -0,0 +1,8 @@
+uno
+
+dos
+tres
+
+
+cinco
+seis
diff --git a/platform/platform-tests/testData/diff/applyPatch/incorrectAlreadyAppliedDetection/after/ResolveCache.java b/platform/platform-tests/testData/diff/applyPatch/incorrectAlreadyAppliedDetection/after/ResolveCache.java
new file mode 100644
index 000000000000..939aef6384c5
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/incorrectAlreadyAppliedDetection/after/ResolveCache.java
@@ -0,0 +1,262 @@
+package com.intellij.psi.impl.source.resolve;
+
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.util.Key;
+import com.intellij.psi.*;
+import com.intellij.psi.impl.PsiManagerImpl;
+import com.intellij.reference.SoftReference;
+import com.intellij.util.ConcurrencyUtil;
+import com.intellij.util.Function;
+import com.intellij.util.containers.ConcurrentWeakHashMap;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class ResolveCache {
+ private static final Key>> JAVA_RESOLVE_MAP = Key.create("ResolveCache.JAVA_RESOLVE_MAP");
+ private static final Key>> RESOLVE_MAP = Key.create("ResolveCache.RESOLVE_MAP");
+ private static final Key>> JAVA_RESOLVE_MAP_INCOMPLETE = Key.create("ResolveCache.JAVA_RESOLVE_MAP_INCOMPLETE");
+ private static final Key>> RESOLVE_MAP_INCOMPLETE = Key.create("ResolveCache.RESOLVE_MAP_INCOMPLETE");
+ private static final Key> IS_BEING_RESOLVED_KEY = Key.create("ResolveCache.IS_BEING_RESOLVED_KEY");
+ private static final Key> VAR_TO_CONST_VALUE_MAP_KEY = Key.create("ResolveCache.VAR_TO_CONST_VALUE_MAP_KEY");
+
+ //store types for method call expressions, NB: this caching is semantical, without this captured wildcards won't work
+ private final ConcurrentWeakHashMap> myCaclulatedlTypes = new ConcurrentWeakHashMap>();
+
+ private static final Object NULL = Key.create("NULL");
+
+ private final PsiManagerImpl myManager;
+
+ private final Map myVarToConstValueMap1;
+ private final Map myVarToConstValueMap2;
+
+ private final Map>[] myPolyVariantResolveMaps = new Map[4];
+ private final Map>[] myResolveMaps = new Map[4];
+ private final AtomicInteger myClearCount = new AtomicInteger(0);
+
+
+ public static interface AbstractResolver[ {
+ Result resolve(Ref ref, boolean incompleteCode);
+ }
+ public static interface PolyVariantResolver extends AbstractResolver {
+ }
+
+ public static interface Resolver extends AbstractResolver{
+ }
+
+ public ResolveCache(PsiManagerImpl manager) {
+ myManager = manager;
+
+ myVarToConstValueMap1 = getOrCreateWeakMap(myManager, VAR_TO_CONST_VALUE_MAP_KEY, true);
+ myVarToConstValueMap2 = getOrCreateWeakMap(myManager, VAR_TO_CONST_VALUE_MAP_KEY, false);
+
+ myPolyVariantResolveMaps[0] = getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP, true);
+ myPolyVariantResolveMaps[1] = getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP_INCOMPLETE, true);
+ myResolveMaps[0] = getOrCreateWeakMap(myManager, RESOLVE_MAP, true);
+ myResolveMaps[1] = getOrCreateWeakMap(myManager, RESOLVE_MAP_INCOMPLETE, true);
+
+ myPolyVariantResolveMaps[2] = getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP, false);
+ myPolyVariantResolveMaps[3] = getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP_INCOMPLETE, false);
+
+ myResolveMaps[2] = getOrCreateWeakMap(myManager, RESOLVE_MAP, false);
+ myResolveMaps[3] = getOrCreateWeakMap(myManager, RESOLVE_MAP_INCOMPLETE, false);
+
+ myManager.registerRunnableToRunOnAnyChange(new Runnable() {
+ public void run() {
+ myCaclulatedlTypes.clear();
+ }
+ });
+ }
+
+ public PsiType getType(PsiExpression expr, Function f) {
+ WeakReference ref = myCaclulatedlTypes.get(expr);
+ PsiType type = ref == null ? null : ref.get();
+ if (type == null) {
+ type = f.fun(expr);
+ WeakReference existingRef = ConcurrencyUtil.cacheOrGet(myCaclulatedlTypes, expr, new WeakReference(type));
+ PsiType existing = existingRef.get();
+ if (existing != null) type = existing;
+ }
+ assert type == null || type.isValid();
+ return type;
+ }
+
+ public void clearCache() {
+ myClearCount.incrementAndGet();
+ getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP, true).clear();
+ getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP_INCOMPLETE, true).clear();
+ getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP, false).clear();
+ getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP_INCOMPLETE, false).clear();
+ getOrCreateWeakMap(myManager, RESOLVE_MAP, true).clear();
+ getOrCreateWeakMap(myManager, RESOLVE_MAP_INCOMPLETE, true).clear();
+ getOrCreateWeakMap(myManager, RESOLVE_MAP, false).clear();
+ getOrCreateWeakMap(myManager, RESOLVE_MAP_INCOMPLETE, false).clear();
+ }
+
+ private ][ Result resolve(Ref ref,
+ AbstractResolver][ resolver,
+ Map][>[] maps,
+ boolean needToPreventRecursion,
+ boolean incompleteCode) {
+ ProgressManager.getInstance().checkCanceled();
+
+ int clearCountOnStart = myClearCount.intValue();
+
+ boolean physical = ref.getElement().isPhysical();
+ Result result = getCached(ref, maps, physical, incompleteCode);
+ if (result != null) {
+ return result;
+ }
+
+ if (incompleteCode) {
+ result = resolve(ref, resolver, maps, needToPreventRecursion, false);
+ if (result != null && !(result instanceof Object[] && ((Object[])result).length == 0)) {
+ cache(ref, result, maps, physical, incompleteCode, clearCountOnStart);
+ return result;
+ }
+ }
+
+ if (needToPreventRecursion && !lockElement(ref)) return null;
+ try {
+ result = resolver.resolve(ref, incompleteCode);
+ }
+ finally {
+ if (needToPreventRecursion) {
+ unlockElement(ref);
+ }
+ }
+ cache(ref, result, maps, physical, incompleteCode, clearCountOnStart);
+ return result;
+ }
+
+ public ResolveResult[] resolveWithCaching(PsiPolyVariantReference ref,
+ PolyVariantResolver resolver,
+ boolean needToPreventRecursion,
+ boolean incompleteCode) {
+ ResolveResult[] result = resolve(ref, resolver, myPolyVariantResolveMaps, needToPreventRecursion, incompleteCode);
+ return result == null ? JavaResolveResult.EMPTY_ARRAY : result;
+ }
+
+ public PsiElement resolveWithCaching(PsiReference ref,
+ Resolver resolver,
+ boolean needToPreventRecursion,
+ boolean incompleteCode) {
+ return resolve(ref, resolver, myResolveMaps, needToPreventRecursion, incompleteCode);
+ }
+
+ private static boolean lockElement(PsiReference ref) {
+ synchronized (IS_BEING_RESOLVED_KEY) {
+ PsiElement elt = ref.getElement();
+
+ List lockingThreads = elt.getUserData(IS_BEING_RESOLVED_KEY);
+ final Thread currentThread = Thread.currentThread();
+ if (lockingThreads == null) {
+ lockingThreads = new ArrayList(1);
+ elt.putUserData(IS_BEING_RESOLVED_KEY, lockingThreads);
+ }
+ else {
+ if (lockingThreads.contains(currentThread)) return false;
+ }
+ lockingThreads.add(currentThread);
+ }
+ return true;
+ }
+
+ private static void unlockElement(PsiReference ref) {
+ synchronized (IS_BEING_RESOLVED_KEY) {
+ PsiElement elt = ref.getElement();
+
+ List lockingThreads = elt.getUserData(IS_BEING_RESOLVED_KEY);
+ if (lockingThreads == null) return;
+ final Thread currentThread = Thread.currentThread();
+ lockingThreads.remove(currentThread);
+ if (lockingThreads.isEmpty()) {
+ elt.putUserData(IS_BEING_RESOLVED_KEY, null);
+ }
+ }
+ }
+
+ //for Visual Fabrique
+ public void clearResolveCaches(PsiReference ref) {
+ myClearCount.incrementAndGet();
+ final boolean physical = ref.getElement().isPhysical();
+ if (ref instanceof PsiPolyVariantReference) {
+ cache((PsiPolyVariantReference)ref, null, myPolyVariantResolveMaps, physical, false, myClearCount.intValue());
+ cache((PsiPolyVariantReference)ref, null, myPolyVariantResolveMaps, physical, true, myClearCount.intValue());
+ }
+ }
+
+
+ private static int getIndex(boolean physical, boolean ic){
+ return (physical ? 0 : 1) << 1 | (ic ? 1 : 0);
+ }
+
+ private static ][Result getCached(Ref ref, Map][>[] maps, boolean physical, boolean ic){
+ int index = getIndex(physical, ic);
+ Reference reference = maps[index].get(ref);
+ if(reference == null) return null;
+ return reference.get();
+ }
+ private ][ void cache(Ref ref, Result result, Map][>[] maps, boolean physical, boolean incompleteCode, final int clearCountOnStart) {
+ if (clearCountOnStart != myClearCount.intValue() && result != null) return;
+
+ int index = getIndex(physical, incompleteCode);
+ maps[index].put(ref, new SoftReference(result));
+ }
+
+ public static interface ConstValueComputer{
+ Object execute(PsiVariable variable, Set visitedVars);
+ }
+
+ public Object computeConstantValueWithCaching(PsiVariable variable, ConstValueComputer computer, Set visitedVars){
+ boolean physical = variable.isPhysical();
+
+ Object cached = (physical ? myVarToConstValueMap1 : myVarToConstValueMap2).get(variable);
+ if (cached == NULL) return null;
+ if (cached != null) return cached;
+
+ Object result = computer.execute(variable, visitedVars);
+
+ (physical ? myVarToConstValueMap1 : myVarToConstValueMap2).put(variable, result != null ? result : NULL);
+
+ return result;
+ }
+
+ public ConcurrentMap getOrCreateWeakMap(final PsiManagerImpl manager, final Key> key, boolean forPhysical) {
+ MapPair pair = manager.getUserData(key);
+ if (pair == null){
+ pair = new MapPair();
+ pair = manager.putUserDataIfAbsent(key, pair);
+
+ final MapPair _pair = pair;
+ manager.registerRunnableToRunOnChange(
+ new Runnable() {
+ public void run() {
+ myClearCount.incrementAndGet();
+ _pair.physicalMap.clear();
+ }
+ }
+ );
+ manager.registerRunnableToRunOnAnyChange(
+ new Runnable() {
+ public void run() {
+ myClearCount.incrementAndGet();
+ _pair.nonPhysicalMap.clear();
+ }
+ }
+ );
+ }
+ return forPhysical ? pair.physicalMap : pair.nonPhysicalMap;
+ }
+
+ public static class MapPair{
+ public final ConcurrentMap physicalMap = new ConcurrentWeakHashMap();
+ public final ConcurrentMap nonPhysicalMap = new ConcurrentWeakHashMap();
+ }
+}
diff --git a/platform/platform-tests/testData/diff/applyPatch/incorrectAlreadyAppliedDetection/apply.patch b/platform/platform-tests/testData/diff/applyPatch/incorrectAlreadyAppliedDetection/apply.patch
new file mode 100644
index 000000000000..ce8873912dfb
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/incorrectAlreadyAppliedDetection/apply.patch
@@ -0,0 +1,430 @@
+--- before/ResolveCache.java (revision 1)
++++ after/ResolveCache.java Thu Feb 01 20:27:02 MSK 2007
+@@ -5,23 +5,29 @@
+ import com.intellij.psi.*;
+ import com.intellij.psi.impl.PsiManagerImpl;
+ import com.intellij.reference.SoftReference;
++import com.intellij.util.ConcurrencyUtil;
+ import com.intellij.util.Function;
+-import com.intellij.util.containers.WeakHashMap;
++import com.intellij.util.containers.ConcurrentWeakHashMap;
+
+ import java.lang.ref.Reference;
+ import java.lang.ref.WeakReference;
+-import java.util.*;
++import java.util.ArrayList;
++import java.util.List;
++import java.util.Map;
++import java.util.Set;
++import java.util.concurrent.ConcurrentMap;
++import java.util.concurrent.atomic.AtomicInteger;
+
+ public class ResolveCache {
+- private static final Key>> JAVA_RESOLVE_MAP = Key.create("ResolveCache.JAVA_RESOLVE_MAP");
++ private static final Key>> JAVA_RESOLVE_MAP = Key.create("ResolveCache.JAVA_RESOLVE_MAP");
+ private static final Key>> RESOLVE_MAP = Key.create("ResolveCache.RESOLVE_MAP");
+- private static final Key>> JAVA_RESOLVE_MAP_INCOMPLETE = Key.create("ResolveCache.JAVA_RESOLVE_MAP_INCOMPLETE");
++ private static final Key>> JAVA_RESOLVE_MAP_INCOMPLETE = Key.create("ResolveCache.JAVA_RESOLVE_MAP_INCOMPLETE");
+ private static final Key>> RESOLVE_MAP_INCOMPLETE = Key.create("ResolveCache.RESOLVE_MAP_INCOMPLETE");
+ private static final Key]> IS_BEING_RESOLVED_KEY = Key.create("ResolveCache.IS_BEING_RESOLVED_KEY");
+ private static final Key> VAR_TO_CONST_VALUE_MAP_KEY = Key.create("ResolveCache.VAR_TO_CONST_VALUE_MAP_KEY");
+
+ //store types for method call expressions, NB: this caching is semantical, without this captured wildcards won't work
+- private Map> myCaclulatedlTypes;
++ private final ConcurrentWeakHashMap> myCaclulatedlTypes = new ConcurrentWeakHashMap>();
+
+ private static final Object NULL = Key.create("NULL");
+
+@@ -30,24 +36,25 @@
+ private final Map myVarToConstValueMap1;
+ private final Map myVarToConstValueMap2;
+
+- private final WeakHashMap[] myPolyVariantResolveMaps = new WeakHashMap[4];
+- private final WeakHashMap[] myResolveMaps = new WeakHashMap[4];
+- private int myClearCount = 0;
++ private final Map>[] myPolyVariantResolveMaps = new Map[4];
++ private final Map>[] myResolveMaps = new Map[4];
++ private final AtomicInteger myClearCount = new AtomicInteger(0);
+
+
+- public static interface PolyVariantResolver {
+- ResolveResult[] resolve(PsiPolyVariantReference ref, boolean incompleteCode);
++ public static interface AbstractResolver[ {
++ Result resolve(Ref ref, boolean incompleteCode);
+ }
++ public static interface PolyVariantResolver extends AbstractResolver {
++ }
+
+- public static interface Resolver{
+- PsiElement resolve(PsiReference ref, boolean incompleteCode);
++ public static interface Resolver extends AbstractResolver{
+ }
+
+ public ResolveCache(PsiManagerImpl manager) {
+ myManager = manager;
+
+- myVarToConstValueMap1 = Collections.synchronizedMap(getOrCreateWeakMap(myManager, VAR_TO_CONST_VALUE_MAP_KEY, true));
+- myVarToConstValueMap2 = Collections.synchronizedMap(getOrCreateWeakMap(myManager, VAR_TO_CONST_VALUE_MAP_KEY, false));
++ myVarToConstValueMap1 = getOrCreateWeakMap(myManager, VAR_TO_CONST_VALUE_MAP_KEY, true);
++ myVarToConstValueMap2 = getOrCreateWeakMap(myManager, VAR_TO_CONST_VALUE_MAP_KEY, false);
+
+ myPolyVariantResolveMaps[0] = getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP, true);
+ myPolyVariantResolveMaps[1] = getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP_INCOMPLETE, true);
+@@ -60,200 +67,147 @@
+ myResolveMaps[2] = getOrCreateWeakMap(myManager, RESOLVE_MAP, false);
+ myResolveMaps[3] = getOrCreateWeakMap(myManager, RESOLVE_MAP_INCOMPLETE, false);
+
+- myCaclulatedlTypes = new WeakHashMap>();
+ myManager.registerRunnableToRunOnAnyChange(new Runnable() {
+ public void run() {
+- synchronized (PsiLock.LOCK) {
+- myCaclulatedlTypes.clear();
+- }
++ myCaclulatedlTypes.clear();
++ }
+- }
+ });
+ }
+
+ public PsiType getType(PsiExpression expr, Function f) {
+- WeakReference ref;
+- synchronized (PsiLock.LOCK) {
+- ref = myCaclulatedlTypes.get(expr);
+- }
++ WeakReference ref = myCaclulatedlTypes.get(expr);
+ PsiType type = ref == null ? null : ref.get();
+ if (type == null) {
+ type = f.fun(expr);
+- synchronized (PsiLock.LOCK) {
+- myCaclulatedlTypes.put(expr, new WeakReference(type));
++ WeakReference existingRef = ConcurrencyUtil.cacheOrGet(myCaclulatedlTypes, expr, new WeakReference(type));
++ PsiType existing = existingRef.get();
++ if (existing != null) type = existing;
+- }
++ }
+- }
+-
++ assert type == null || type.isValid();
+ return type;
+ }
+
+ public void clearCache() {
+- synchronized (PsiLock.LOCK) {
+- myClearCount++;
++ myClearCount.incrementAndGet();
+- getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP, true).clear();
+- getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP_INCOMPLETE, true).clear();
+- getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP, false).clear();
+- getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP_INCOMPLETE, false).clear();
+- getOrCreateWeakMap(myManager, RESOLVE_MAP, true).clear();
+- getOrCreateWeakMap(myManager, RESOLVE_MAP_INCOMPLETE, true).clear();
+- getOrCreateWeakMap(myManager, RESOLVE_MAP, false).clear();
+- getOrCreateWeakMap(myManager, RESOLVE_MAP_INCOMPLETE, false).clear();
+- }
++ getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP, true).clear();
++ getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP_INCOMPLETE, true).clear();
++ getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP, false).clear();
++ getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP_INCOMPLETE, false).clear();
++ getOrCreateWeakMap(myManager, RESOLVE_MAP, true).clear();
++ getOrCreateWeakMap(myManager, RESOLVE_MAP_INCOMPLETE, true).clear();
++ getOrCreateWeakMap(myManager, RESOLVE_MAP, false).clear();
++ getOrCreateWeakMap(myManager, RESOLVE_MAP_INCOMPLETE, false).clear();
++ }
+- }
+
+- public PsiElement resolveWithCaching(PsiReference ref,
+- Resolver resolver,
++ private ][ Result resolve(Ref ref,
++ AbstractResolver][ resolver,
++ Map][>[] maps,
+- boolean needToPreventRecursion,
+- boolean incompleteCode) {
++ boolean needToPreventRecursion,
++ boolean incompleteCode) {
+ ProgressManager.getInstance().checkCanceled();
+
+- int clearCountOnStart;
+- synchronized (PsiLock.LOCK) {
+- clearCountOnStart = myClearCount;
+- }
++ int clearCountOnStart = myClearCount.intValue();
+
+ boolean physical = ref.getElement().isPhysical();
+- final Reference cached = getCachedResolve(ref, physical, incompleteCode);
+- if (cached != null) return cached.get();
++ Result result = getCached(ref, maps, physical, incompleteCode);
++ if (result != null) {
++ return result;
++ }
+-
++
+ if (incompleteCode) {
+- final PsiElement results = resolveWithCaching(ref, resolver, needToPreventRecursion, false);
+- if (results != null) {
+- setCachedResolve(ref, results, physical, true, clearCountOnStart);
+- return results;
++ result = resolve(ref, resolver, maps, needToPreventRecursion, false);
++ if (result != null && !(result instanceof Object[] && ((Object[])result).length == 0)) {
++ cache(ref, result, maps, physical, incompleteCode, clearCountOnStart);
++ return result;
+ }
+ }
+
+- if (!lockElement(ref, needToPreventRecursion)) return null;
+- PsiElement result = null;
++ if (needToPreventRecursion && !lockElement(ref)) return null;
+ try {
+ result = resolver.resolve(ref, incompleteCode);
+ }
+- finally{
++ finally {
+- unlockElement(ref, needToPreventRecursion);
++ if (needToPreventRecursion) {
++ unlockElement(ref);
+- }
++ }
+-
+- setCachedResolve(ref, result, physical, incompleteCode, clearCountOnStart);
++ }
++ cache(ref, result, maps, physical, incompleteCode, clearCountOnStart);
+ return result;
+ }
+
+- private static boolean lockElement(PsiReference ref, boolean doLock) {
+- if (doLock) {
++ public ResolveResult[] resolveWithCaching(PsiPolyVariantReference ref,
++ PolyVariantResolver resolver,
++ boolean needToPreventRecursion,
++ boolean incompleteCode) {
++ ResolveResult[] result = resolve(ref, resolver, myPolyVariantResolveMaps, needToPreventRecursion, incompleteCode);
++ return result == null ? JavaResolveResult.EMPTY_ARRAY : result;
++ }
++
++ public PsiElement resolveWithCaching(PsiReference ref,
++ Resolver resolver,
++ boolean needToPreventRecursion,
++ boolean incompleteCode) {
++ return resolve(ref, resolver, myResolveMaps, needToPreventRecursion, incompleteCode);
++ }
++
++ private static boolean lockElement(PsiReference ref) {
+- synchronized (IS_BEING_RESOLVED_KEY) {
+- PsiElement elt = ref.getElement();
++ synchronized (IS_BEING_RESOLVED_KEY) {
++ PsiElement elt = ref.getElement();
+
+- List lockingThreads = elt.getUserData(IS_BEING_RESOLVED_KEY);
+- final Thread currentThread = Thread.currentThread();
+- if (lockingThreads == null) {
+- lockingThreads = new ArrayList(1);
+- elt.putUserData(IS_BEING_RESOLVED_KEY, lockingThreads);
+- }
+- else {
+- if (lockingThreads.contains(currentThread)) return false;
+- }
+- lockingThreads.add(currentThread);
+- }
++ List lockingThreads = elt.getUserData(IS_BEING_RESOLVED_KEY);
++ final Thread currentThread = Thread.currentThread();
++ if (lockingThreads == null) {
++ lockingThreads = new ArrayList(1);
++ elt.putUserData(IS_BEING_RESOLVED_KEY, lockingThreads);
++ }
++ else {
++ if (lockingThreads.contains(currentThread)) return false;
++ }
++ lockingThreads.add(currentThread);
++ }
+- }
+ return true;
+ }
+
+- private static void unlockElement(PsiReference ref, boolean doLock) {
+- if (doLock) {
++ private static void unlockElement(PsiReference ref) {
+- synchronized (IS_BEING_RESOLVED_KEY) {
+- PsiElement elt = ref.getElement();
++ synchronized (IS_BEING_RESOLVED_KEY) {
++ PsiElement elt = ref.getElement();
+
+- List lockingThreads = elt.getUserData(IS_BEING_RESOLVED_KEY);
+- if (lockingThreads == null) return;
+- final Thread currentThread = Thread.currentThread();
+- lockingThreads.remove(currentThread);
+- if (lockingThreads.isEmpty()) {
+- elt.putUserData(IS_BEING_RESOLVED_KEY, null);
+- }
+- }
+- }
++ List lockingThreads = elt.getUserData(IS_BEING_RESOLVED_KEY);
++ if (lockingThreads == null) return;
++ final Thread currentThread = Thread.currentThread();
++ lockingThreads.remove(currentThread);
++ if (lockingThreads.isEmpty()) {
++ elt.putUserData(IS_BEING_RESOLVED_KEY, null);
++ }
++ }
++ }
+- }
+
+- private void setCachedResolve(PsiReference ref, PsiElement results, boolean physical, boolean incompleteCode, final int clearCountOnStart) {
+- synchronized (PsiLock.LOCK) {
+- if (clearCountOnStart != myClearCount && results != null) return;
+-
+- int index = getIndex(physical, incompleteCode);
+- myResolveMaps[index].put(ref, new SoftReference(results));
+- }
+- }
+-
+ //for Visual Fabrique
+ public void clearResolveCaches(PsiReference ref) {
+- synchronized (PsiLock.LOCK) {
+- myClearCount++;
++ myClearCount.incrementAndGet();
+- final boolean physical = ref.getElement().isPhysical();
++ final boolean physical = ref.getElement().isPhysical();
+- setCachedPolyVariantResolve(ref, null, physical, false, myClearCount);
+- setCachedPolyVariantResolve(ref, null, physical, true, myClearCount);
++ if (ref instanceof PsiPolyVariantReference) {
++ cache((PsiPolyVariantReference)ref, null, myPolyVariantResolveMaps, physical, false, myClearCount.intValue());
++ cache((PsiPolyVariantReference)ref, null, myPolyVariantResolveMaps, physical, true, myClearCount.intValue());
+ }
+ }
+
+- private Reference getCachedResolve(PsiReference ref, boolean physical, boolean incompleteCode) {
+- synchronized (PsiLock.LOCK) {
+- int index = getIndex(physical, incompleteCode);
+- final Reference reference = (Reference)myResolveMaps[index].get(ref);
+- if(reference == null) return null;
+- return reference;
+- }
+- }
+
+- public ResolveResult[] resolveWithCaching(PsiPolyVariantReference ref,
+- PolyVariantResolver resolver,
+- boolean needToPreventRecursion,
+- boolean incompleteCode) {
+- ProgressManager.getInstance().checkCanceled();
+-
+- int clearCountOnStart;
+- synchronized (PsiLock.LOCK) {
+- clearCountOnStart = myClearCount;
+- }
+-
+- boolean physical = ref.getElement().isPhysical();
+- final ResolveResult[] cached = getCachedPolyVariantResolve(ref, physical, incompleteCode);
+- if (cached != null) return cached;
+-
+- if (incompleteCode) {
+- final ResolveResult[] results = resolveWithCaching(ref, resolver, needToPreventRecursion, false);
+- if (results != null && results.length > 0) {
+- setCachedPolyVariantResolve(ref, results, physical, true, clearCountOnStart);
+- return results;
+- }
+- }
+-
+- if (!lockElement(ref, needToPreventRecursion)) return JavaResolveResult.EMPTY_ARRAY;
+- ResolveResult[] result;
+- try {
+- result = resolver.resolve(ref, incompleteCode);
+- } finally {
+- unlockElement(ref, needToPreventRecursion);
+- }
+-
+- setCachedPolyVariantResolve(ref, result, physical, incompleteCode, clearCountOnStart);
+- return result;
+- }
+-
+ private static int getIndex(boolean physical, boolean ic){
+ return (physical ? 0 : 1) << 1 | (ic ? 1 : 0);
+ }
+
+- private void setCachedPolyVariantResolve(PsiReference ref, ResolveResult[] result, boolean physical, boolean incomplete, int clearCountOnStart){
+- synchronized (PsiLock.LOCK) {
+- if (clearCountOnStart != myClearCount && result != null) return;
+- int index = getIndex(physical, incomplete);
+- myPolyVariantResolveMaps[index].put(ref, new SoftReference(result));
+- }
+- }
+-
+- private ResolveResult[] getCachedPolyVariantResolve(PsiReference ref, boolean physical, boolean ic){
+- synchronized (PsiLock.LOCK) {
++ private static ][Result getCached(Ref ref, Map][>[] maps, boolean physical, boolean ic){
+- int index = getIndex(physical, ic);
++ int index = getIndex(physical, ic);
+- final Reference reference = (Reference)myPolyVariantResolveMaps[index].get(ref);
++ Reference reference = maps[index].get(ref);
+- if(reference == null) return null;
+- return reference.get();
+- }
++ if(reference == null) return null;
++ return reference.get();
++ }
++ private ][ void cache(Ref ref, Result result, Map][>[] maps, boolean physical, boolean incompleteCode, final int clearCountOnStart) {
++ if (clearCountOnStart != myClearCount.intValue() && result != null) return;
++
++ int index = getIndex(physical, incompleteCode);
++ maps[index].put(ref, new SoftReference(result));
+ }
+
+ public static interface ConstValueComputer{
+@@ -274,44 +228,35 @@
+ return result;
+ }
+
+- public WeakHashMap getOrCreateWeakMap(final PsiManagerImpl manager, final Key> key, boolean forPhysical) {
++ public ConcurrentMap getOrCreateWeakMap(final PsiManagerImpl manager, final Key> key, boolean forPhysical) {
+ MapPair pair = manager.getUserData(key);
+ if (pair == null){
+ pair = new MapPair();
+- manager.putUserData(key, pair);
++ pair = manager.putUserDataIfAbsent(key, pair);
+
+ final MapPair _pair = pair;
+ manager.registerRunnableToRunOnChange(
+ new Runnable() {
+ public void run() {
+- synchronized (PsiLock.LOCK) {
+- myClearCount++;
++ myClearCount.incrementAndGet();
+- _pair.physicalMap.clear();
+- }
+- }
++ _pair.physicalMap.clear();
++ }
++ }
+- }
+ );
+ manager.registerRunnableToRunOnAnyChange(
+ new Runnable() {
+ public void run() {
+- synchronized (PsiLock.LOCK) {
+- myClearCount++;
++ myClearCount.incrementAndGet();
+- _pair.nonPhysicalMap.clear();
+- }
+- }
++ _pair.nonPhysicalMap.clear();
++ }
++ }
+- }
+ );
+ }
+ return forPhysical ? pair.physicalMap : pair.nonPhysicalMap;
+ }
+
+ public static class MapPair{
+- public WeakHashMap physicalMap;
+- public WeakHashMap nonPhysicalMap;
+-
+- public MapPair() {
+- physicalMap = new WeakHashMap();
+- nonPhysicalMap = new WeakHashMap();
++ public final ConcurrentMap physicalMap = new ConcurrentWeakHashMap();
++ public final ConcurrentMap nonPhysicalMap = new ConcurrentWeakHashMap();
+- }
+- }
++ }
++}
+\ No newline at end of file
+-}
diff --git a/platform/platform-tests/testData/diff/applyPatch/incorrectAlreadyAppliedDetection/before/ResolveCache.java b/platform/platform-tests/testData/diff/applyPatch/incorrectAlreadyAppliedDetection/before/ResolveCache.java
new file mode 100644
index 000000000000..0b3ce28385c5
--- /dev/null
+++ b/platform/platform-tests/testData/diff/applyPatch/incorrectAlreadyAppliedDetection/before/ResolveCache.java
@@ -0,0 +1,317 @@
+package com.intellij.psi.impl.source.resolve;
+
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.util.Key;
+import com.intellij.psi.*;
+import com.intellij.psi.impl.PsiManagerImpl;
+import com.intellij.reference.SoftReference;
+import com.intellij.util.Function;
+import com.intellij.util.containers.WeakHashMap;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.*;
+
+public class ResolveCache {
+ private static final Key>> JAVA_RESOLVE_MAP = Key.create("ResolveCache.JAVA_RESOLVE_MAP");
+ private static final Key>> RESOLVE_MAP = Key.create("ResolveCache.RESOLVE_MAP");
+ private static final Key>> JAVA_RESOLVE_MAP_INCOMPLETE = Key.create("ResolveCache.JAVA_RESOLVE_MAP_INCOMPLETE");
+ private static final Key>> RESOLVE_MAP_INCOMPLETE = Key.create("ResolveCache.RESOLVE_MAP_INCOMPLETE");
+ private static final Key]> IS_BEING_RESOLVED_KEY = Key.create("ResolveCache.IS_BEING_RESOLVED_KEY");
+ private static final Key> VAR_TO_CONST_VALUE_MAP_KEY = Key.create("ResolveCache.VAR_TO_CONST_VALUE_MAP_KEY");
+
+ //store types for method call expressions, NB: this caching is semantical, without this captured wildcards won't work
+ private Map> myCaclulatedlTypes;
+
+ private static final Object NULL = Key.create("NULL");
+
+ private final PsiManagerImpl myManager;
+
+ private final Map myVarToConstValueMap1;
+ private final Map myVarToConstValueMap2;
+
+ private final WeakHashMap[] myPolyVariantResolveMaps = new WeakHashMap[4];
+ private final WeakHashMap[] myResolveMaps = new WeakHashMap[4];
+ private int myClearCount = 0;
+
+
+ public static interface PolyVariantResolver {
+ ResolveResult[] resolve(PsiPolyVariantReference ref, boolean incompleteCode);
+ }
+
+ public static interface Resolver{
+ PsiElement resolve(PsiReference ref, boolean incompleteCode);
+ }
+
+ public ResolveCache(PsiManagerImpl manager) {
+ myManager = manager;
+
+ myVarToConstValueMap1 = Collections.synchronizedMap(getOrCreateWeakMap(myManager, VAR_TO_CONST_VALUE_MAP_KEY, true));
+ myVarToConstValueMap2 = Collections.synchronizedMap(getOrCreateWeakMap(myManager, VAR_TO_CONST_VALUE_MAP_KEY, false));
+
+ myPolyVariantResolveMaps[0] = getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP, true);
+ myPolyVariantResolveMaps[1] = getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP_INCOMPLETE, true);
+ myResolveMaps[0] = getOrCreateWeakMap(myManager, RESOLVE_MAP, true);
+ myResolveMaps[1] = getOrCreateWeakMap(myManager, RESOLVE_MAP_INCOMPLETE, true);
+
+ myPolyVariantResolveMaps[2] = getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP, false);
+ myPolyVariantResolveMaps[3] = getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP_INCOMPLETE, false);
+
+ myResolveMaps[2] = getOrCreateWeakMap(myManager, RESOLVE_MAP, false);
+ myResolveMaps[3] = getOrCreateWeakMap(myManager, RESOLVE_MAP_INCOMPLETE, false);
+
+ myCaclulatedlTypes = new WeakHashMap>();
+ myManager.registerRunnableToRunOnAnyChange(new Runnable() {
+ public void run() {
+ synchronized (PsiLock.LOCK) {
+ myCaclulatedlTypes.clear();
+ }
+ }
+ });
+ }
+
+ public PsiType getType(PsiExpression expr, Function f) {
+ WeakReference ref;
+ synchronized (PsiLock.LOCK) {
+ ref = myCaclulatedlTypes.get(expr);
+ }
+ PsiType type = ref == null ? null : ref.get();
+ if (type == null) {
+ type = f.fun(expr);
+ synchronized (PsiLock.LOCK) {
+ myCaclulatedlTypes.put(expr, new WeakReference(type));
+ }
+ }
+
+ return type;
+ }
+
+ public void clearCache() {
+ synchronized (PsiLock.LOCK) {
+ myClearCount++;
+ getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP, true).clear();
+ getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP_INCOMPLETE, true).clear();
+ getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP, false).clear();
+ getOrCreateWeakMap(myManager, JAVA_RESOLVE_MAP_INCOMPLETE, false).clear();
+ getOrCreateWeakMap(myManager, RESOLVE_MAP, true).clear();
+ getOrCreateWeakMap(myManager, RESOLVE_MAP_INCOMPLETE, true).clear();
+ getOrCreateWeakMap(myManager, RESOLVE_MAP, false).clear();
+ getOrCreateWeakMap(myManager, RESOLVE_MAP_INCOMPLETE, false).clear();
+ }
+ }
+
+ public PsiElement resolveWithCaching(PsiReference ref,
+ Resolver resolver,
+ boolean needToPreventRecursion,
+ boolean incompleteCode) {
+ ProgressManager.getInstance().checkCanceled();
+
+ int clearCountOnStart;
+ synchronized (PsiLock.LOCK) {
+ clearCountOnStart = myClearCount;
+ }
+
+ boolean physical = ref.getElement().isPhysical();
+ final Reference cached = getCachedResolve(ref, physical, incompleteCode);
+ if (cached != null) return cached.get();
+
+ if (incompleteCode) {
+ final PsiElement results = resolveWithCaching(ref, resolver, needToPreventRecursion, false);
+ if (results != null) {
+ setCachedResolve(ref, results, physical, true, clearCountOnStart);
+ return results;
+ }
+ }
+
+ if (!lockElement(ref, needToPreventRecursion)) return null;
+ PsiElement result = null;
+ try {
+ result = resolver.resolve(ref, incompleteCode);
+ }
+ finally{
+ unlockElement(ref, needToPreventRecursion);
+ }
+
+ setCachedResolve(ref, result, physical, incompleteCode, clearCountOnStart);
+ return result;
+ }
+
+ private static boolean lockElement(PsiReference ref, boolean doLock) {
+ if (doLock) {
+ synchronized (IS_BEING_RESOLVED_KEY) {
+ PsiElement elt = ref.getElement();
+
+ List