Implement Android resource reference folding
#KT-15451 Fixed
This commit is contained in:
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="4u7">
|
||||
<words>
|
||||
<w>foldable</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
||||
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.addImport.AbstractAddImportTest
|
||||
import org.jetbrains.kotlin.allopen.AbstractBytecodeListingTestForAllOpen
|
||||
import org.jetbrains.kotlin.android.*
|
||||
import org.jetbrains.kotlin.android.configure.AbstractConfigureProjectTest
|
||||
import org.jetbrains.kotlin.android.folding.AbstractAndroidResourceFoldingTest
|
||||
import org.jetbrains.kotlin.android.intentions.AbstractAndroidIntentionTest
|
||||
import org.jetbrains.kotlin.android.intentions.AbstractAndroidResourceIntentionTest
|
||||
import org.jetbrains.kotlin.android.lint.AbstractKotlinLintTest
|
||||
@@ -1222,6 +1223,10 @@ fun main(args: Array<String>) {
|
||||
testClass<AbstractAndroidIntentionTest> {
|
||||
model("android/intentions", pattern = "^([\\w\\-_]+)\\.kt$")
|
||||
}
|
||||
|
||||
testClass<AbstractAndroidResourceFoldingTest> {
|
||||
model("android/folding")
|
||||
}
|
||||
}
|
||||
|
||||
testGroup("plugins/plugins-tests/tests", "plugins/android-extensions/android-extensions-jps/testData") {
|
||||
|
||||
@@ -21,5 +21,6 @@
|
||||
<orderEntry type="module" module-name="tests-common" scope="TEST" />
|
||||
<orderEntry type="module" module-name="idea-core" />
|
||||
<orderEntry type="module" module-name="lint-idea" scope="TEST" />
|
||||
<orderEntry type="library" name="uast-java" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright 2010-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 org.jetbrains.kotlin.android.folding
|
||||
|
||||
import com.android.SdkConstants.*
|
||||
import com.android.ide.common.resources.configuration.FolderConfiguration
|
||||
import com.android.ide.common.resources.configuration.LocaleQualifier
|
||||
import com.android.resources.ResourceType
|
||||
import com.android.tools.idea.folding.AndroidFoldingSettings
|
||||
import com.android.tools.idea.rendering.AppResourceRepository
|
||||
import com.android.tools.idea.rendering.LocalResourceRepository
|
||||
import com.intellij.lang.ASTNode
|
||||
import com.intellij.lang.folding.FoldingBuilderEx
|
||||
import com.intellij.lang.folding.FoldingDescriptor
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiClassOwner
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.impl.source.SourceTreeToPsiMap
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.uast.*
|
||||
import org.jetbrains.uast.visitor.AbstractUastVisitor
|
||||
import java.util.regex.Pattern
|
||||
|
||||
|
||||
class ResourceFoldingBuilder : FoldingBuilderEx() {
|
||||
|
||||
companion object {
|
||||
// See lint's StringFormatDetector
|
||||
private val FORMAT = Pattern.compile("%(\\d+\\$)?([-+#, 0(<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])")
|
||||
private val FOLD_MAX_LENGTH = 60
|
||||
private val FORCE_PROJECT_RESOURCE_LOADING = true
|
||||
private val UNIT_TEST_MODE: Boolean = ApplicationManager.getApplication().isUnitTestMode
|
||||
private val RESOURCE_TYPES = listOf(ResourceType.STRING,
|
||||
ResourceType.DIMEN,
|
||||
ResourceType.INTEGER,
|
||||
ResourceType.PLURALS)
|
||||
}
|
||||
|
||||
private val isFoldingEnabled = AndroidFoldingSettings.getInstance().isCollapseAndroidStrings
|
||||
|
||||
override fun getPlaceholderText(node: ASTNode): String? {
|
||||
|
||||
tailrec fun UElement.unwrapReferenceAndGetValue(resources: LocalResourceRepository): String? = when (this) {
|
||||
is UQualifiedReferenceExpression -> selector.unwrapReferenceAndGetValue(resources)
|
||||
is UCallExpression -> (valueArguments.firstOrNull() as? UReferenceExpression)?.getAndroidResourceValue(resources, this)
|
||||
else -> (this as? UReferenceExpression)?.getAndroidResourceValue(resources)
|
||||
}
|
||||
|
||||
val element = SourceTreeToPsiMap.treeElementToPsi(node) ?: return null
|
||||
val appResources = getAppResources(element) ?: return null
|
||||
val uastContext = ServiceManager.getService(element.project, UastContext::class.java) ?: return null
|
||||
return uastContext.convertElement(element, null, null)?.unwrapReferenceAndGetValue(appResources)
|
||||
}
|
||||
|
||||
override fun buildFoldRegions(root: PsiElement, document: Document, quick: Boolean): Array<FoldingDescriptor> {
|
||||
if (root !is KtFile || quick && !UNIT_TEST_MODE || !isFoldingEnabled) {
|
||||
return emptyArray()
|
||||
}
|
||||
|
||||
val file = root.toUElement()
|
||||
val result = arrayListOf<FoldingDescriptor>()
|
||||
file?.accept(object : AbstractUastVisitor() {
|
||||
override fun visitSimpleNameReferenceExpression(node: USimpleNameReferenceExpression): Boolean {
|
||||
node.getFoldingDescriptor()?.let { result.add(it) }
|
||||
return super.visitSimpleNameReferenceExpression(node)
|
||||
}
|
||||
})
|
||||
|
||||
return result.toTypedArray()
|
||||
}
|
||||
|
||||
override fun isCollapsedByDefault(node: ASTNode): Boolean = isFoldingEnabled
|
||||
|
||||
private fun UReferenceExpression.getFoldingDescriptor(): FoldingDescriptor? {
|
||||
val resolved = resolve() ?: return null
|
||||
val resourceType = resolved.getAndroidResourceType() ?: return null
|
||||
if (resourceType !in RESOURCE_TYPES) return null
|
||||
|
||||
fun UElement.createFoldingDescriptor() = psi?.let { psi ->
|
||||
val dependencies: Set<PsiElement> = setOf(psi)
|
||||
FoldingDescriptor(psi.node, psi.textRange, null, dependencies)
|
||||
}
|
||||
|
||||
val element = getOutermostQualified() ?: this
|
||||
|
||||
(element.containingElement as? UCallExpression)?.run {
|
||||
if (isFoldableGetResourceValueCall()) {
|
||||
return getOutermostQualified()?.createFoldingDescriptor() ?: createFoldingDescriptor()
|
||||
}
|
||||
}
|
||||
|
||||
return element.createFoldingDescriptor()
|
||||
}
|
||||
|
||||
private fun UCallExpression.isFoldableGetResourceValueCall() =
|
||||
methodName == "getString" ||
|
||||
methodName == "getText" ||
|
||||
methodName == "getInteger" ||
|
||||
methodName?.startsWith("getDimension") ?: false ||
|
||||
methodName?.startsWith("getQuantityString") ?: false
|
||||
|
||||
private fun PsiElement.getAndroidResourceType(): ResourceType? {
|
||||
val elementType = parent as? PsiClass ?: return null
|
||||
val elementPackage = elementType.parent as? PsiClass ?: return null
|
||||
if (R_CLASS != elementPackage.name) return null
|
||||
if (elementPackage.qualifiedName != "$ANDROID_PKG.$R_CLASS") {
|
||||
return ResourceType.getEnum(elementType.name)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun UReferenceExpression.getAndroidResourceValue(resources: LocalResourceRepository, call: UCallExpression? = null): String? {
|
||||
val resourceType = resolve()?.getAndroidResourceType() ?: return null
|
||||
val referenceConfig = FolderConfiguration().apply { localeQualifier = LocaleQualifier("xx") }
|
||||
val key = resolvedName
|
||||
val resourceValue = resources.getConfiguredValue(resourceType, key, referenceConfig)?.value ?: return null
|
||||
val text = if (call != null) formatArguments(call, resourceValue) else resourceValue
|
||||
|
||||
if (resourceType == ResourceType.PLURALS && text.startsWith(STRING_PREFIX)) {
|
||||
resources.getConfiguredValue(ResourceType.STRING, text.substring(STRING_PREFIX.length), referenceConfig)?.value?.let {
|
||||
return '"' + StringUtil.shortenTextWithEllipsis(it, FOLD_MAX_LENGTH - 2, 0) + '"'
|
||||
}
|
||||
}
|
||||
|
||||
if (resourceType == ResourceType.STRING) {
|
||||
return '"' + StringUtil.shortenTextWithEllipsis(text, FOLD_MAX_LENGTH - 2, 0) + '"'
|
||||
}
|
||||
else if (text.length <= 1) {
|
||||
// Don't just inline empty or one-character replacements: they can't be expanded by a mouse click
|
||||
// so are hard to use without knowing about the folding keyboard shortcut to toggle folding.
|
||||
// This is similar to how IntelliJ 14 handles call parameters
|
||||
return key + ": " + text
|
||||
}
|
||||
|
||||
return StringUtil.shortenTextWithEllipsis(text, FOLD_MAX_LENGTH, 0)
|
||||
}
|
||||
|
||||
// Converted from com.android.tools.idea.folding.InlinedResource#insertArguments
|
||||
private fun formatArguments(callExpression: UCallExpression, formatString: String): String {
|
||||
if (!formatString.contains('%')) {
|
||||
return formatString
|
||||
}
|
||||
|
||||
val args = callExpression.valueArguments
|
||||
if (args.isEmpty() || !args.first().isPsiValid) {
|
||||
return formatString
|
||||
}
|
||||
|
||||
val matcher = FORMAT.matcher(formatString)
|
||||
var index = 0
|
||||
var prevIndex = 0
|
||||
var nextNumber = 1
|
||||
var start = 0
|
||||
val sb = StringBuilder(2 * formatString.length)
|
||||
while (true) {
|
||||
if (matcher.find(index)) {
|
||||
if ("%" == matcher.group(6)) {
|
||||
index = matcher.end()
|
||||
continue
|
||||
}
|
||||
val matchStart = matcher.start()
|
||||
// Make sure this is not an escaped '%'
|
||||
while (prevIndex < matchStart) {
|
||||
val c = formatString[prevIndex]
|
||||
if (c == '\\') {
|
||||
prevIndex++
|
||||
}
|
||||
prevIndex++
|
||||
}
|
||||
if (prevIndex > matchStart) {
|
||||
// We're in an escape, ignore this result
|
||||
index = prevIndex
|
||||
continue
|
||||
}
|
||||
|
||||
index = matcher.end()
|
||||
|
||||
// Shouldn't throw a number format exception since we've already
|
||||
// matched the pattern in the regexp
|
||||
val number: Int
|
||||
var numberString: String? = matcher.group(1)
|
||||
if (numberString != null) {
|
||||
// Strip off trailing $
|
||||
numberString = numberString.substring(0, numberString.length - 1)
|
||||
number = Integer.parseInt(numberString)
|
||||
nextNumber = number + 1
|
||||
}
|
||||
else {
|
||||
number = nextNumber++
|
||||
}
|
||||
|
||||
if (number > 0 && number < args.size) {
|
||||
val argExpression = args[number]
|
||||
var value: Any? = argExpression.evaluate()
|
||||
|
||||
if (value == null) {
|
||||
value = args[number].asSourceString()
|
||||
}
|
||||
|
||||
for (i in start..matchStart - 1) {
|
||||
sb.append(formatString[i])
|
||||
}
|
||||
|
||||
sb.append("{")
|
||||
sb.append(value)
|
||||
sb.append('}')
|
||||
start = index
|
||||
}
|
||||
}
|
||||
else {
|
||||
var i = start
|
||||
val n = formatString.length
|
||||
while (i < n) {
|
||||
sb.append(formatString[i])
|
||||
i++
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun getAppResources(element: PsiElement): LocalResourceRepository? = ModuleUtilCore.findModuleForPsiElement(element)?.let {
|
||||
AppResourceRepository.getAppResources(it, FORCE_PROJECT_RESOURCE_LOADING)
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2010-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 org.jetbrains.kotlin.android.folding
|
||||
|
||||
import com.android.SdkConstants
|
||||
import org.jetbrains.kotlin.android.KotlinAndroidTestCase
|
||||
|
||||
|
||||
abstract class AbstractAndroidResourceFoldingTest : KotlinAndroidTestCase() {
|
||||
|
||||
fun doTest(path: String) {
|
||||
myFixture.copyFileToProject("values.xml", "res/values/values.xml")
|
||||
myFixture.copyFileToProject("R.java", "gen/com/myapp/R.java")
|
||||
myFixture.testFoldingWithCollapseStatus(path, "${myFixture.tempDirPath}/src/main.kt")
|
||||
}
|
||||
|
||||
override fun createManifest() {
|
||||
myFixture.copyFileToProject("idea/testData/android/AndroidManifest.xml", SdkConstants.FN_ANDROID_MANIFEST_XML)
|
||||
}
|
||||
|
||||
override fun getTestDataPath() = "idea/testData/android/folding"
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2010-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 org.jetbrains.kotlin.android.folding;
|
||||
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils;
|
||||
import org.jetbrains.kotlin.test.TargetBackend;
|
||||
import org.jetbrains.kotlin.test.TestMetadata;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
||||
@SuppressWarnings("all")
|
||||
@TestMetadata("idea/testData/android/folding")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class AndroidResourceFoldingTestGenerated extends AbstractAndroidResourceFoldingTest {
|
||||
public void testAllFilesPresentInFolding() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/android/folding"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("dimensions.kt")
|
||||
public void testDimensions() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/folding/dimensions.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("getString.kt")
|
||||
public void testGetString() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/folding/getString.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("plurals.kt")
|
||||
public void testPlurals() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/folding/plurals.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,8 @@
|
||||
<codeInsight.unresolvedReferenceQuickFixProvider
|
||||
implementation="org.jetbrains.kotlin.android.inspection.KotlinAndroidResourceQuickFixProvider"/>
|
||||
|
||||
<lang.foldingBuilder language="kotlin" implementationClass="org.jetbrains.kotlin.android.folding.ResourceFoldingBuilder" />
|
||||
|
||||
</extensions>
|
||||
|
||||
<extensions defaultExtensionNs="org.jetbrains.kotlin">
|
||||
|
||||
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
package com.myapp;
|
||||
|
||||
public final class R {
|
||||
public static final class string {
|
||||
public static final int app_title=0x7f060022;
|
||||
public static final int compex_format_string=0x7f060023;
|
||||
public static final int empty_string=0x7f060024;
|
||||
public static final int format_string=0x7f060025;
|
||||
public static final int invalid_format=0x7f060026;
|
||||
public static final int long_string=0x7f060027;
|
||||
public static final int plural_few=0x7f060028;
|
||||
public static final int plural_one=0x7f060029;
|
||||
public static final int plural_other=0x7f06002a;
|
||||
public static final int positive_button_text=0x7f06002b;
|
||||
public static final int string1=0x7f06002c;
|
||||
public static final int string2=0x7f06002d;
|
||||
public static final int string3=0x7f06002e;
|
||||
}
|
||||
public static final class integer {
|
||||
public static final int some_int=0x7f0c0003;
|
||||
public static final int max_action_buttons=0x7f0c0004;
|
||||
}
|
||||
public static final class plurals {
|
||||
public static final int first=0x7f0d0000;
|
||||
public static final int second=0x7f0d0001;
|
||||
}
|
||||
public static final class dimen {
|
||||
public static final int action_bar_default_height=0x7f07004f;
|
||||
public static final int action_bar_icon_vertical_padding=0x7f070050;
|
||||
public static final int mydimen1=0x7f07005b;
|
||||
public static final int mydimen2=0x7f07005c;
|
||||
public static final int mydimen3=0x7f07005d;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.myapp
|
||||
|
||||
import <fold text='...' expand='false'>android.app.Activity
|
||||
import android.app.AlertDialog</fold>
|
||||
|
||||
class MyActivity : Activity() <fold text='{...}' expand='true'>{
|
||||
fun test() <fold text='{...}' expand='true'>{
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
.setView(null)
|
||||
.setPositiveButton(<fold text='"Yes"' expand='false'>R.string.positive_button_text</fold>, null)
|
||||
.create()
|
||||
val dimension = <fold text='56dip' expand='false'>resources.getDimension(R.dimen.action_bar_default_height)</fold>
|
||||
val dimension2 = <fold text='4dip' expand='false'>resources.getDimensionPixelOffset(R.dimen.action_bar_icon_vertical_padding)</fold>
|
||||
val dimension3 = <fold text='3dip' expand='false'>resources.getDimensionPixelSize(R.dimen.mydimen3)</fold>
|
||||
val strings = intArrayOf(<fold text='"1111"' expand='false'>R.string.string1</fold>, <fold text='"2222"' expand='false'>R.string.string2</fold>, <fold text='"3333"' expand='false'>R.string.string3</fold>)
|
||||
val dimensions = intArrayOf(<fold text='1dip' expand='false'>R.dimen.mydimen1</fold>, <fold text='2dip' expand='false'>R.dimen.mydimen2</fold>)
|
||||
val maxButtons = <fold text='max_action_buttons: 2' expand='false'>resources.getInteger(R.integer.max_action_buttons)</fold>
|
||||
}</fold>
|
||||
}</fold>
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.myapp
|
||||
|
||||
import <fold text='...' expand='false'>android.content.Context
|
||||
import com.myapp.R.string.*;</fold>
|
||||
|
||||
fun Context.getAppTitle() = <fold text='"My Cool Application"' expand='false'>getString(R.string.app_title)</fold>
|
||||
|
||||
fun getAppTitle(context: Context) <fold text='{...}' expand='true'>{
|
||||
return <fold text='"My Cool Application"' expand='false'>context.getString(app_title)</fold>
|
||||
}</fold>
|
||||
|
||||
fun getLongString(context: Context) = <fold text='"Some very very long string which should be trimmed. Lor..."' expand='false'>context.getString(R.string.long_string)</fold>
|
||||
|
||||
fun bar(text: String) = text
|
||||
|
||||
fun foo(context: Context) <fold text='{...}' expand='true'>{
|
||||
val name = "Vasya"
|
||||
val otherName = context.getString(R.string.no_resource)
|
||||
val cancel = context.getString(android.R.string.cancel)
|
||||
val text = <fold text='"Hello {Vasya}!"' expand='false'>context.getString(R.string.format_string, name)</fold>
|
||||
val text = <fold text='"Hello {otherName}!"' expand='false'>context.getString(R.string.format_string, otherName)</fold>
|
||||
val emptyString = <fold text='""' expand='false'>context.getString(R.string.empty_string)</fold>
|
||||
val someInt = <fold text='some_int: 8' expand='false'>context.resources.getInteger(R.integer.some_int)</fold>
|
||||
context.getString(R.string.no_resource)
|
||||
bar(<fold text='"Third: {333} Repeated: {333} First: {111} Second: {222}"' expand='false'>context.getResources().getString(R.string.compex_format_string, "111", "222", "333")</fold>)
|
||||
val invalid = with(context) { getString(<fold text='"Escaped: %s First: %1$s Invalid: %20$s"' expand='false'>R.string.invalid_format</fold>, name, otherName) }
|
||||
}</fold>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.myapp
|
||||
|
||||
import android.content.Context
|
||||
|
||||
fun Context.testPlurals(quantity: Int) <fold text='{...}' expand='true'>{
|
||||
val plural1 = <fold text='"Reference One"' expand='false'>resources.getQuantityString(R.plurals.first, quantity)</fold>
|
||||
val plural2 = <fold text='Quantity One' expand='false'>this.getResources().getQuantityString(R.plurals.second, quantity)</fold>
|
||||
}</fold>
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<resources>
|
||||
<string name="app_name">ResourceFolding</string>
|
||||
<string name="app_title">My Cool Application</string>
|
||||
<string name="long_string">Some very very long string which should be trimmed. Lorem ipsum dolor sit amet.</string>
|
||||
<string name="format_string">Hello %s!</string>
|
||||
<string name="empty_string" />
|
||||
<string name="compex_format_string" formatted="false">Third: %3$s Repeated: %3$s First: %1$s Second: %2$d</string>
|
||||
<string name="invalid_format" formatted="false">Escaped: \%s First: %1$s Invalid: %20$s</string>
|
||||
|
||||
<string name="plural_one">Reference One</string>
|
||||
<string name="plural_few">Reference Few</string>
|
||||
<string name="plural_other">Reference Other</string>
|
||||
|
||||
<string name="positive_button_text">Yes</string>
|
||||
<string name="string1">1111</string>
|
||||
<string name="string2">2222</string>
|
||||
<string name="string3">3333</string>
|
||||
|
||||
<integer name="some_int">8</integer>
|
||||
<integer name="max_action_buttons">2</integer>
|
||||
|
||||
<plurals name="first">
|
||||
<item quantity="one">@string/plural_one</item>
|
||||
<item quantity="few">@string/plural_few</item>
|
||||
<item quantity="other">@string/plural_other</item>
|
||||
</plurals>
|
||||
<plurals name="second">
|
||||
<item quantity="one">Quantity One</item>
|
||||
<item quantity="few">Quantity Few</item>
|
||||
</plurals>
|
||||
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
<dimen name="action_bar_default_height">56dip</dimen>
|
||||
<dimen name="action_bar_icon_vertical_padding">4dip</dimen>
|
||||
<dimen name="mydimen3">3dip</dimen>
|
||||
<dimen name="mydimen1">1dip</dimen>
|
||||
<dimen name="mydimen2">2dip</dimen>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user