diff --git a/.idea/libraries/intellilang_plugin.xml b/.idea/libraries/intellilang_plugin.xml
new file mode 100644
index 00000000000..1a00de34e63
--- /dev/null
+++ b/.idea/libraries/intellilang_plugin.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetStringTemplateExpression.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetStringTemplateExpression.java
index 6ccfb13c7d9..882827f5818 100644
--- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetStringTemplateExpression.java
+++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetStringTemplateExpression.java
@@ -17,9 +17,18 @@
package org.jetbrains.jet.lang.psi;
import com.intellij.lang.ASTNode;
+import com.intellij.psi.ElementManipulators;
+import com.intellij.psi.LiteralTextEscaper;
+import com.intellij.psi.PsiLanguageInjectionHost;
+import com.intellij.psi.tree.TokenSet;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jet.JetNodeTypes;
+import org.jetbrains.jet.lexer.JetTokens;
+
+
+public class JetStringTemplateExpression extends JetExpressionImpl implements PsiLanguageInjectionHost {
+ private static final TokenSet TOKENS_SUITABLE_FOR_INJECTION = TokenSet.create(JetNodeTypes.LITERAL_STRING_TEMPLATE_ENTRY, JetNodeTypes.ESCAPE_STRING_TEMPLATE_ENTRY);
-public class JetStringTemplateExpression extends JetExpressionImpl {
public JetStringTemplateExpression(@NotNull ASTNode node) {
super(node);
}
@@ -33,4 +42,27 @@ public class JetStringTemplateExpression extends JetExpressionImpl {
public JetStringTemplateEntry[] getEntries() {
return findChildrenByClass(JetStringTemplateEntry.class);
}
+
+ @Override
+ public boolean isValidHost() {
+ ASTNode node = getNode();
+ ASTNode child = node.getFirstChildNode().getTreeNext();
+ while (child != null) {
+ if (child.getElementType() == JetTokens.CLOSING_QUOTE) return true;
+ if (!TOKENS_SUITABLE_FOR_INJECTION.contains(child.getElementType())) return false;
+ child = child.getTreeNext();
+ }
+ return false;
+ }
+
+ @Override
+ public PsiLanguageInjectionHost updateText(@NotNull String text) {
+ return ElementManipulators.handleContentChange(this, text);
+ }
+
+ @NotNull
+ @Override
+ public LiteralTextEscaper extends PsiLanguageInjectionHost> createLiteralTextEscaper() {
+ return new KotlinStringLiteralTextEscaper(this);
+ }
}
diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/KotlinStringLiteralTextEscaper.kt b/compiler/frontend/src/org/jetbrains/jet/lang/psi/KotlinStringLiteralTextEscaper.kt
new file mode 100644
index 00000000000..a5c9b31ad11
--- /dev/null
+++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/KotlinStringLiteralTextEscaper.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2010-2014 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 org.jetbrains.jet.lang.psi
+
+import com.intellij.psi.LiteralTextEscaper
+import com.intellij.openapi.util.TextRange
+import gnu.trove.TIntArrayList
+import org.jetbrains.jet.lang.psi.psiUtil.getContentRange
+import org.jetbrains.jet.lang.psi.psiUtil.isSingleQuoted
+
+public class KotlinStringLiteralTextEscaper(host: JetStringTemplateExpression): LiteralTextEscaper(host) {
+ private var sourceOffsets: IntArray? = null
+
+ override fun decode(rangeInsideHost: TextRange, outChars: StringBuilder): Boolean {
+ val sourceOffsetsList = TIntArrayList()
+ var sourceOffset = 0
+
+ for (child in myHost.getEntries()) {
+ val childRange = TextRange.from(child.getStartOffsetInParent(), child.getTextLength())
+ if (rangeInsideHost.getEndOffset() <= childRange.getStartOffset()) {
+ break
+ }
+ if (childRange.getEndOffset() <= rangeInsideHost.getStartOffset()) {
+ continue
+ }
+ when (child) {
+ is JetLiteralStringTemplateEntry -> {
+ val textRange = rangeInsideHost.intersection(childRange).shiftRight(-childRange.getStartOffset())
+ outChars.append(child.getText(), textRange.getStartOffset(), textRange.getEndOffset())
+ textRange.getLength().times {
+ sourceOffsetsList.add(sourceOffset++)
+ }
+ }
+ is JetEscapeStringTemplateEntry -> {
+ if (!rangeInsideHost.contains(childRange)) {
+ //don't allow injection if its range starts or ends inside escaped sequence
+ return false
+ }
+ val unescaped = child.getUnescapedValue()
+ outChars.append(unescaped)
+ unescaped.length().times {
+ sourceOffsetsList.add(sourceOffset)
+ }
+ sourceOffset += child.getTextLength()
+ }
+ else -> return false
+ }
+ }
+ sourceOffsetsList.add(sourceOffset)
+ sourceOffsets = sourceOffsetsList.toNativeArray()
+ return true
+ }
+
+ override fun getOffsetInHost(offsetInDecoded: Int, rangeInsideHost: TextRange): Int {
+ val offsets = sourceOffsets
+ if (offsets == null || offsetInDecoded >= offsets.size()) return -1
+ return Math.min(offsets[offsetInDecoded], rangeInsideHost.getLength()) + rangeInsideHost.getStartOffset()
+ }
+
+ override fun getRelevantTextRange(): TextRange {
+ return myHost.getContentRange()
+ }
+
+ override fun isOneLine(): Boolean {
+ return myHost.isSingleQuoted()
+ }
+}
\ No newline at end of file
diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/JetStringTemplateExpressionManipulator.kt b/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/JetStringTemplateExpressionManipulator.kt
new file mode 100644
index 00000000000..6d81d9d2d91
--- /dev/null
+++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/JetStringTemplateExpressionManipulator.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2014 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 org.jetbrains.jet.lang.psi.psiUtil
+
+import com.intellij.psi.AbstractElementManipulator
+import org.jetbrains.jet.lang.psi.JetStringTemplateExpression
+import com.intellij.openapi.util.TextRange
+import org.jetbrains.jet.lang.psi.JetPsiFactory
+import com.intellij.openapi.util.text.StringUtil
+
+public class JetStringTemplateExpressionManipulator : AbstractElementManipulator() {
+ override fun handleContentChange(element: JetStringTemplateExpression, range: TextRange, newContent: String): JetStringTemplateExpression? {
+ val node = element.getNode()
+ val content = if (element.isSingleQuoted()) StringUtil.escapeStringCharacters(newContent) else newContent
+ val oldText = node.getText()
+ val newText = oldText.substring(0, range.getStartOffset()) + content + oldText.substring(range.getEndOffset())
+ val expression = JetPsiFactory(element.getProject()).createExpression(newText)
+ node.replaceAllChildrenToChildrenOf(expression.getNode())
+ return node.getPsi(javaClass())
+ }
+
+ override fun getRangeInElement(element: JetStringTemplateExpression): TextRange {
+ return element.getContentRange()
+ }
+}
\ No newline at end of file
diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/StringTemplateExpressionManipulator.kt b/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/StringTemplateExpressionManipulator.kt
new file mode 100644
index 00000000000..7c56aa4e038
--- /dev/null
+++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/StringTemplateExpressionManipulator.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2014 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 org.jetbrains.jet.lang.psi.psiUtil
+
+import com.intellij.psi.AbstractElementManipulator
+import org.jetbrains.jet.lang.psi.JetStringTemplateExpression
+import com.intellij.openapi.util.TextRange
+import org.jetbrains.jet.lang.psi.JetPsiFactory
+import com.intellij.openapi.util.text.StringUtil
+
+public class StringTemplateExpressionManipulator: AbstractElementManipulator() {
+ override fun handleContentChange(element: JetStringTemplateExpression, range: TextRange, newContent: String): JetStringTemplateExpression? {
+ val node = element.getNode()
+ val content = if (node.getFirstChildNode().getTextLength() == 1) StringUtil.escapeStringCharacters(newContent) else newContent
+ val oldText = node.getText()
+ val newText = oldText.substring(0, range.getStartOffset()) + content + oldText.substring(range.getEndOffset())
+ val expression = JetPsiFactory(element.getProject()).createExpression(newText)
+ node.replaceAllChildrenToChildrenOf(expression.getNode())
+ return node.getPsi(javaClass())
+ }
+
+ override fun getRangeInElement(element: JetStringTemplateExpression): TextRange {
+ return element.getContentRange()
+ }
+}
\ No newline at end of file
diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/jetPsiUtil.kt b/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/jetPsiUtil.kt
index 93b35105160..378ff386c97 100644
--- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/jetPsiUtil.kt
+++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/psiUtil/jetPsiUtil.kt
@@ -38,6 +38,7 @@ import org.jetbrains.jet.lang.diagnostics.DiagnosticUtils
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.PsiComment
import org.jetbrains.jet.lang.resolve.calls.CallTransformer.CallForImplicitInvoke
+import com.intellij.openapi.util.TextRange
public fun JetCallElement.getCallNameExpression(): JetSimpleNameExpression? {
val calleeExpression = getCalleeExpression()
@@ -409,4 +410,14 @@ public fun JetTypeReference?.isProbablyNothing(): Boolean {
}
public fun JetUserType?.isProbablyNothing(): Boolean
- = this?.getReferencedName() == "Nothing"
\ No newline at end of file
+ = this?.getReferencedName() == "Nothing"
+
+public fun JetStringTemplateExpression.getContentRange(): TextRange {
+ val start = getNode().getFirstChildNode().getTextLength()
+ val lastChild = getNode().getLastChildNode()
+ val length = getTextLength()
+ return TextRange(start, if (lastChild.getElementType() == JetTokens.CLOSING_QUOTE) length - lastChild.getTextLength() else length)
+}
+
+public fun JetStringTemplateExpression.isSingleQuoted(): Boolean
+ = getNode().getFirstChildNode().getTextLength() == 1
diff --git a/idea/idea.iml b/idea/idea.iml
index 607544ed1b5..21412d50803 100644
--- a/idea/idea.iml
+++ b/idea/idea.iml
@@ -24,6 +24,7 @@
+
@@ -44,5 +45,4 @@
-
-
+
\ No newline at end of file
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 388bc265dbb..2f695c90203 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -221,6 +221,8 @@
+
diff --git a/idea/tests/org/jetbrains/jet/plugin/DirectiveBasedActionUtils.java b/idea/tests/org/jetbrains/jet/plugin/DirectiveBasedActionUtils.java
index 71c3a865df4..33c5aa41c6e 100644
--- a/idea/tests/org/jetbrains/jet/plugin/DirectiveBasedActionUtils.java
+++ b/idea/tests/org/jetbrains/jet/plugin/DirectiveBasedActionUtils.java
@@ -105,5 +105,5 @@ public class DirectiveBasedActionUtils {
}
private static final Collection IRRELEVANT_ACTION_PREFIXES =
- Arrays.asList("Disable ", "Edit intention settings", "Edit inspection profile setting");
+ Arrays.asList("Disable ", "Edit intention settings", "Edit inspection profile setting", "Inject Language/Reference");
}
diff --git a/idea/tests/org/jetbrains/jet/psi/StringTemplateExpressionManipulatorTest.kt b/idea/tests/org/jetbrains/jet/psi/StringTemplateExpressionManipulatorTest.kt
new file mode 100644
index 00000000000..9f86abc1598
--- /dev/null
+++ b/idea/tests/org/jetbrains/jet/psi/StringTemplateExpressionManipulatorTest.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2010-2014 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 org.jetbrains.jet.psi
+
+import org.jetbrains.jet.plugin.JetLightCodeInsightFixtureTestCase
+import org.jetbrains.jet.plugin.JetLightProjectDescriptor
+import org.jetbrains.jet.lang.psi.JetStringTemplateExpression
+import org.jetbrains.jet.lang.psi.JetPsiFactory
+import com.intellij.psi.ElementManipulators
+import org.junit.Assert.*
+import com.intellij.openapi.util.TextRange
+
+public class StringTemplateExpressionManipulatorTest: JetLightCodeInsightFixtureTestCase() {
+ public fun testSingleQuoted() {
+ doTestContentChange("\"a\"", "b", "\"b\"")
+ doTestContentChange("\"\"", "b", "\"b\"")
+ doTestContentChange("\"a\"", "\t", "\"\\t\"")
+ doTestContentChange("\"a\"", "\n", "\"\\n\"")
+ doTestContentChange("\"a\"", "\\t", "\"\\\\t\"")
+ }
+
+ public fun testUnclosedQuoted() {
+ doTestContentChange("\"a", "b", "\"b")
+ doTestContentChange("\"", "b", "\"b")
+ doTestContentChange("\"a", "\t", "\"\\t")
+ doTestContentChange("\"a", "\n", "\"\\n")
+ doTestContentChange("\"a", "\\t", "\"\\\\t")
+ }
+
+ public fun testTripleQuoted() {
+ doTestContentChange("\"\"\"a\"\"\"", "b", "\"\"\"b\"\"\"")
+ doTestContentChange("\"\"\"\"\"\"", "b", "\"\"\"b\"\"\"")
+ doTestContentChange("\"\"\"a\"\"\"", "\t", "\"\"\"\t\"\"\"")
+ doTestContentChange("\"\"\"a\"\"\"", "\n", "\"\"\"\n\"\"\"")
+ doTestContentChange("\"\"\"a\"\"\"", "\\t", "\"\"\"\\t\"\"\"")
+ }
+
+ public fun testUnclosedTripleQuoted() {
+ doTestContentChange("\"\"\"a", "b", "\"\"\"b")
+ doTestContentChange("\"\"\"", "b", "\"\"\"b")
+ doTestContentChange("\"\"\"a", "\t", "\"\"\"\t")
+ doTestContentChange("\"\"\"a", "\n", "\"\"\"\n")
+ doTestContentChange("\"\"\"a", "\\t", "\"\"\"\\t")
+ }
+
+ public fun testReplaceRange() {
+ doTestContentChange("\"abc\"", "x", range = TextRange(2,3), expected = "\"axc\"")
+ doTestContentChange("\"\"\"abc\"\"\"", "x", range = TextRange(4,5), expected = "\"\"\"axc\"\"\"")
+ }
+
+ private fun doTestContentChange(original: String, newContent: String, expected: String, range: TextRange? = null) {
+ val expression = JetPsiFactory(getProject()).createExpression(original) as JetStringTemplateExpression
+ val manipulator = ElementManipulators.getNotNullManipulator(expression)
+ val newExpression = if (range == null) manipulator.handleContentChange(expression, newContent) else manipulator.handleContentChange(expression, range, newContent)
+ assertEquals(expected, newExpression.getText())
+ }
+
+ override fun getProjectDescriptor() = JetLightProjectDescriptor.INSTANCE
+}
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/jet/psi/injection/StringInjectionHostTest.kt b/idea/tests/org/jetbrains/jet/psi/injection/StringInjectionHostTest.kt
new file mode 100644
index 00000000000..785acba3b76
--- /dev/null
+++ b/idea/tests/org/jetbrains/jet/psi/injection/StringInjectionHostTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2010-2014 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 org.jetbrains.jet.psi.injection
+
+import org.jetbrains.jet.lang.psi.JetPsiFactory
+import org.jetbrains.jet.lang.psi.JetStringTemplateExpression
+import org.junit.Assert.*
+import com.intellij.openapi.util.TextRange
+import java.util.HashMap
+import org.jetbrains.jet.JetLiteFixture
+import org.jetbrains.jet.ConfigurationKind
+
+public class StringInjectionHostTest: JetLiteFixture() {
+ public fun testRegular() {
+ with (quoted("")) {
+ checkInjection("", mapOf(0 to 1))
+ assertOneLine()
+ }
+ with (quoted("a")) {
+ checkInjection("a", mapOf(0 to 1, 1 to 2))
+ assertOneLine()
+ }
+ with (quoted("ab")) {
+ checkInjection("ab", mapOf(0 to 1, 1 to 2, 2 to 3))
+ checkInjection("a", mapOf(0 to 1, 1 to 2), rangeInHost = TextRange(1, 2))
+ checkInjection("b", mapOf(0 to 2, 1 to 3), rangeInHost = TextRange(2, 3))
+ assertOneLine()
+ }
+ }
+
+ public fun testUnclosedSimpleLiteral() {
+ assertFalse(stringExpression("\"").isValidHost());
+ assertFalse(stringExpression("\"a").isValidHost());
+ }
+
+ public fun testEscapeSequences() {
+ with (quoted("\\t")) {
+ checkInjection("\t", mapOf(0 to 1, 1 to 3))
+ assertNoInjection(TextRange(1, 2))
+ assertNoInjection(TextRange(2, 3))
+ assertOneLine()
+ }
+
+ with (quoted("a\\tb")) {
+ checkInjection("a\tb", mapOf(0 to 1, 1 to 2, 2 to 4, 3 to 5))
+ checkInjection("a", mapOf(0 to 1, 1 to 2), rangeInHost = TextRange(1, 2))
+ assertNoInjection(TextRange(1, 3))
+ checkInjection("a\t", mapOf(0 to 1, 1 to 2, 2 to 4), rangeInHost = TextRange(1, 4))
+ checkInjection("\t", mapOf(0 to 2, 1 to 4), rangeInHost = TextRange(2, 4))
+ checkInjection("\tb", mapOf(0 to 2, 1 to 4, 2 to 5), rangeInHost = TextRange(2, 5))
+ assertOneLine()
+ }
+ }
+
+ public fun testTripleQuotes() {
+ with (tripleQuoted("")) {
+ checkInjection("", mapOf(0 to 3))
+ assertMultiLine()
+ }
+ with (tripleQuoted("a")) {
+ checkInjection("a", mapOf(0 to 3, 1 to 4))
+ assertMultiLine()
+ }
+ with (tripleQuoted("ab")) {
+ checkInjection("ab", mapOf(0 to 3, 1 to 4, 2 to 5))
+ checkInjection("a", mapOf(0 to 3, 1 to 4), rangeInHost = TextRange(3, 4))
+ checkInjection("b", mapOf(0 to 4, 1 to 5), rangeInHost = TextRange(4, 5))
+ assertMultiLine()
+ }
+ }
+
+ public fun testEscapeSequenceInTripleQuotes() {
+ with (tripleQuoted("\\t")) {
+ checkInjection("\\t", mapOf(0 to 3, 1 to 4, 2 to 5))
+ checkInjection("\\", mapOf(0 to 3, 1 to 4), rangeInHost = TextRange(3, 4))
+ checkInjection("t", mapOf(0 to 4, 1 to 5), rangeInHost = TextRange(4, 5))
+ assertMultiLine()
+ }
+ }
+
+ public fun testMultiLine() {
+ with (tripleQuoted("a\nb")) {
+ checkInjection("a\nb", mapOf(0 to 3, 1 to 4, 2 to 5, 3 to 6))
+ assertMultiLine()
+ }
+ }
+
+ private fun quoted(s: String): JetStringTemplateExpression {
+ return stringExpression("\"$s\"")
+ }
+
+ private fun tripleQuoted(s: String): JetStringTemplateExpression {
+ return stringExpression("\"\"\"$s\"\"\"")
+ }
+
+ private fun stringExpression(s: String): JetStringTemplateExpression {
+ return JetPsiFactory(getProject()).createExpression(s) as JetStringTemplateExpression
+ }
+
+ private fun JetStringTemplateExpression.assertNoInjection(range: TextRange): JetStringTemplateExpression {
+ assertTrue(isValidHost())
+ assertFalse(createLiteralTextEscaper().decode(range, StringBuilder()))
+ return this
+ }
+
+ private fun JetStringTemplateExpression.assertOneLine() {
+ assertTrue(createLiteralTextEscaper().isOneLine())
+ }
+
+ private fun JetStringTemplateExpression.assertMultiLine() {
+ assertFalse(createLiteralTextEscaper().isOneLine())
+ }
+
+ //todo[nik] make private when KT-6382 is fixed
+ fun JetStringTemplateExpression.checkInjection(decoded: String, targetToSourceOffsets: Map, rangeInHost: TextRange? = null) {
+ assertTrue(isValidHost())
+ for (prefix in listOf("", "prefix")) {
+ val escaper = createLiteralTextEscaper()
+ val chars = StringBuilder(prefix)
+ val range = rangeInHost ?: escaper.getRelevantTextRange()
+ assertTrue(escaper.decode(range, chars))
+ assertEquals(decoded, chars.substring(prefix.length()))
+ val extendedOffsets = HashMap(targetToSourceOffsets)
+ val beforeStart = targetToSourceOffsets.keySet().min()!! - 1
+ if (beforeStart >= 0) {
+ extendedOffsets[beforeStart] = -1
+ }
+ extendedOffsets[targetToSourceOffsets.keySet().max()!! + 1] = -1
+ for ((target, source) in extendedOffsets) {
+ assertEquals("Wrong source offset for $target", source, escaper.getOffsetInHost(target, range))
+ }
+ }
+ }
+
+ override fun createEnvironment() = createEnvironmentWithMockJdk(ConfigurationKind.JDK_ONLY)
+}