Merge remote-tracking branch 'origin/master'
@@ -124,5 +124,6 @@
|
||||
<orderEntry type="module" module-name="remote-servers-git-java" />
|
||||
<orderEntry type="module" module-name="terminal" />
|
||||
<orderEntry type="module" module-name="javac-ref-scanner-8" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="platform-build-scripts" scope="TEST" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -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.
|
||||
@@ -355,7 +355,9 @@ public class DebugProcessEvents extends DebugProcessImpl {
|
||||
private void createStackCapturingBreakpoints() {
|
||||
if (Registry.is("debugger.capture.points")) {
|
||||
for (CapturePoint point : DebuggerSettings.getInstance().getCapturePoints()) {
|
||||
StackCapturingLineBreakpoint.track(this, point.myClassName, point.myMethodName, null, point.myParamNo);
|
||||
if (point.myEnabled) {
|
||||
StackCapturingLineBreakpoint.track(this, point.myClassName, point.myMethodName, null, point.myParamNo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@ import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.search.searches.OverridingMethodsSearch;
|
||||
import com.intellij.psi.util.ClassUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.refactoring.changeSignature.MethodNodeBase;
|
||||
import com.intellij.refactoring.changeSignature.inCallers.JavaCallerChooser;
|
||||
import com.intellij.refactoring.changeSignature.inCallers.JavaMethodNode;
|
||||
@@ -53,8 +55,7 @@ abstract class MakeStaticJavaCallerChooser extends JavaCallerChooser {
|
||||
OverridingMethodsSearch.search(containingMethod).findFirst() == null) {
|
||||
final PsiClass containingClass = containingMethod.getContainingClass();
|
||||
if (containingClass != null) {
|
||||
final PsiClass gContainingClass = containingClass.getContainingClass();
|
||||
if (gContainingClass == null || gContainingClass.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
if (ClassUtil.isTopLevelClass(containingClass) || containingClass.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
final InternalUsageInfo[] refsInMember = MakeStaticUtil.findClassRefsInMember(containingMethod, true);
|
||||
for (InternalUsageInfo info : refsInMember) {
|
||||
final PsiElement referencedElement = info.getReferencedElement();
|
||||
|
||||
@@ -8,4 +8,13 @@ class P {
|
||||
bazz(k);
|
||||
bazz(k);
|
||||
}
|
||||
|
||||
void m(int k) {
|
||||
new Runnable() {
|
||||
public void run() {}
|
||||
void mm() {
|
||||
bazz(k);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -8,4 +8,13 @@ class P {
|
||||
bazz(k);
|
||||
bazz(k);
|
||||
}
|
||||
|
||||
void m(int k) {
|
||||
new Runnable() {
|
||||
public void run() {}
|
||||
void mm() {
|
||||
bazz(k);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* Copyright 2000-2016 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.build.scripts
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import org.jetbrains.jps.model.JpsSimpleElement
|
||||
import org.jetbrains.jps.model.java.JavaSourceRootProperties
|
||||
import org.jetbrains.jps.model.java.JavaSourceRootType
|
||||
import org.jetbrains.jps.model.module.JpsModule
|
||||
import org.jetbrains.jps.util.JpsPathUtil
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.io.StringReader
|
||||
import java.util.*
|
||||
import java.util.function.Function
|
||||
|
||||
class IconsClassGenerator(val projectHome: File, val util: JpsModule) {
|
||||
fun processModule(module: JpsModule) {
|
||||
val customLoad: Boolean
|
||||
val packageName: String
|
||||
val className: String
|
||||
val outFile: File
|
||||
if ("icons" == module.name) {
|
||||
customLoad = false
|
||||
packageName = "com.intellij.icons"
|
||||
className = "AllIcons"
|
||||
|
||||
val dir = util.getSourceRoots(JavaSourceRootType.SOURCE).first().file.absolutePath + "/com/intellij/icons"
|
||||
outFile = File(dir, "AllIcons.java")
|
||||
}
|
||||
else {
|
||||
customLoad = true
|
||||
packageName = "icons"
|
||||
|
||||
val firstRoot = module.getSourceRoots(JavaSourceRootType.SOURCE).firstOrNull()
|
||||
if (firstRoot == null) return
|
||||
|
||||
val generatedRoot = module.getSourceRoots(JavaSourceRootType.SOURCE).find { it.properties.isForGeneratedSources }
|
||||
val targetRoot = File((generatedRoot ?: firstRoot).file, "icons")
|
||||
|
||||
val firstRootDir = File(firstRoot.file, "icons")
|
||||
if (firstRootDir.isDirectory && firstRootDir.list().isEmpty()) {
|
||||
//this is added to remove unneeded empty directories created by previous version of this script
|
||||
println("deleting empty directory ${firstRootDir.absolutePath}")
|
||||
firstRootDir.delete()
|
||||
}
|
||||
|
||||
var oldClassName = findIconClass(firstRootDir)
|
||||
if (generatedRoot != null && oldClassName != null) {
|
||||
val oldFile = File(firstRootDir, "${oldClassName}.java")
|
||||
println("deleting $oldFile from source root which isn't marked as 'generated'")
|
||||
oldFile.delete()
|
||||
}
|
||||
if (oldClassName == null) {
|
||||
oldClassName = findIconClass(targetRoot)
|
||||
}
|
||||
|
||||
className = oldClassName ?: directoryName(module) + "Icons"
|
||||
outFile = File(targetRoot, "${className}.java")
|
||||
}
|
||||
|
||||
val copyrightComment = getCopyrightComment(outFile)
|
||||
val text = generate(module, className, packageName, customLoad, copyrightComment)
|
||||
if (text != null) {
|
||||
if (!outFile.exists() || !sameLines(outFile.readText(), text)) {
|
||||
outFile.parentFile.mkdirs()
|
||||
outFile.writeText(text)
|
||||
println("Updated icons class: ${outFile.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findIconClass(dir: File): String? {
|
||||
var className: String? = null
|
||||
dir.children.forEach {
|
||||
if (it.name.endsWith("Icons.java")) {
|
||||
className = it.name.substring(0, it.name.length - ".java".length)
|
||||
}
|
||||
}
|
||||
return className
|
||||
}
|
||||
|
||||
private fun getCopyrightComment(file: File): String {
|
||||
if (!file.isFile) return ""
|
||||
val text = file.readText()
|
||||
val i = text.indexOf("package ")
|
||||
if (i == -1) return ""
|
||||
val comment = text.substring(0, i)
|
||||
return if (comment.trim().endsWith("*/")) comment else ""
|
||||
}
|
||||
|
||||
private fun sameLines(a: String, b: String): Boolean {
|
||||
val ra = BufferedReader(StringReader(a))
|
||||
val rb = BufferedReader(StringReader(b))
|
||||
|
||||
while (true) {
|
||||
val la = ra.readLine()
|
||||
val lb = rb.readLine()
|
||||
if (la != lb) return false
|
||||
if (la == null) return true
|
||||
}
|
||||
}
|
||||
|
||||
private fun generate(module: JpsModule, className: String, packageName: String, customLoad: Boolean, copyrightComment: String): String? {
|
||||
val answer = StringBuilder()
|
||||
answer.append(copyrightComment)
|
||||
append(answer, "package $packageName;\n", 0)
|
||||
append(answer, "import com.intellij.openapi.util.IconLoader;", 0)
|
||||
append(answer, "", 0)
|
||||
append(answer, "import javax.swing.*;", 0)
|
||||
append(answer, "", 0)
|
||||
|
||||
// IconsGeneratedSourcesFilter depends on following comment, if you going to change the text
|
||||
// please do corresponding changes in IconsGeneratedSourcesFilter as well
|
||||
append(answer, "/**", 0)
|
||||
append(answer, " * NOTE THIS FILE IS AUTO-GENERATED", 0)
|
||||
append(answer, " * DO NOT EDIT IT BY HAND, run build/scripts/icons.gant instead", 0)
|
||||
append(answer, " */", 0)
|
||||
|
||||
append(answer, "public class $className {", 0)
|
||||
if (customLoad) {
|
||||
append(answer, "private static Icon load(String path) {", 1)
|
||||
append(answer, "return IconLoader.getIcon(path, ${className}.class);", 2)
|
||||
append(answer, "}", 1)
|
||||
append(answer, "", 0)
|
||||
}
|
||||
|
||||
val imageCollector = ImageCollector(projectHome, true)
|
||||
val images = imageCollector.collect(module)
|
||||
imageCollector.printUsedIconRobots()
|
||||
|
||||
val inners = StringBuilder()
|
||||
processIcons(images, inners, customLoad, 0)
|
||||
if (inners.isEmpty()) return null
|
||||
|
||||
answer.append(inners)
|
||||
append(answer, "}", 0)
|
||||
return answer.toString()
|
||||
}
|
||||
|
||||
private fun processIcons(images: List<ImagePaths>, answer: StringBuilder, customLoad: Boolean, depth: Int) {
|
||||
val level = depth + 1
|
||||
|
||||
val (nodes, leafs) = images.partition { getImageId(it, depth).contains('/') }
|
||||
val nodeMap = nodes.groupBy { getImageId(it, depth).substringBefore('/') }
|
||||
val leafMap = ContainerUtil.newMapFromValues(leafs.iterator(), { getImageId(it, depth) })
|
||||
|
||||
val sortedKeys = (nodeMap.keys + leafMap.keys).sortedWith(NAME_COMPARATOR)
|
||||
sortedKeys.forEach { key ->
|
||||
val group = nodeMap[key]
|
||||
val image = leafMap[key]
|
||||
assert(group == null || image == null)
|
||||
|
||||
if (group != null) {
|
||||
val inners = StringBuilder()
|
||||
processIcons(group, inners, customLoad, depth + 1)
|
||||
|
||||
if (inners.isNotEmpty()) {
|
||||
append(answer, "", level)
|
||||
append(answer, "public static class " + className(key) + " {", level)
|
||||
append(answer, inners.toString(), 0)
|
||||
append(answer, "}", level)
|
||||
}
|
||||
}
|
||||
|
||||
if (image != null) {
|
||||
val file = image.file
|
||||
if (file != null) {
|
||||
val name = file.name
|
||||
val used = image.used
|
||||
val deprecated = image.deprecated
|
||||
|
||||
if (isIcon(file)) {
|
||||
if (used || deprecated) {
|
||||
append(answer, "", level)
|
||||
append(answer, "@SuppressWarnings(\"unused\")", level)
|
||||
}
|
||||
if (deprecated) {
|
||||
append(answer, "@Deprecated", level)
|
||||
}
|
||||
|
||||
val sourceRoot = image.sourceRoot
|
||||
var root_prefix: String = ""
|
||||
if (sourceRoot.rootType == JavaSourceRootType.SOURCE) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val packagePrefix = (sourceRoot.properties as JpsSimpleElement<JavaSourceRootProperties>).data.packagePrefix
|
||||
if (!packagePrefix.isEmpty()) root_prefix = "/" + packagePrefix.replace('.', '/')
|
||||
}
|
||||
|
||||
val size = imageSize(file)
|
||||
val method = if (customLoad) "load" else "IconLoader.getIcon"
|
||||
val relativePath = root_prefix + "/" + FileUtil.getRelativePath(sourceRoot.file, file)!!.replace('\\', '/')
|
||||
append(answer,
|
||||
"public static final Icon ${iconName(name)} = $method(\"$relativePath\"); // ${size.width}x${size.height}",
|
||||
level)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun append(answer: StringBuilder, text: String, level: Int) {
|
||||
answer.append(" ".repeat(level))
|
||||
answer.append(text).append("\n")
|
||||
}
|
||||
|
||||
private fun getImageId(image: ImagePaths, depth: Int): String {
|
||||
val path = StringUtil.trimStart(image.id, "/").split("/")
|
||||
if (path.size < depth) throw IllegalArgumentException("Can't get image ID - ${image.id}, $depth")
|
||||
return path.drop(depth).joinToString("/")
|
||||
}
|
||||
|
||||
private fun directoryName(module: JpsModule): String {
|
||||
return directoryNameFromConfig(module) ?: className(module.name)
|
||||
}
|
||||
|
||||
private fun directoryNameFromConfig(module: JpsModule): String? {
|
||||
val rootUrl = getFirstContentRootUrl(module) ?: return null
|
||||
val rootDir = File(JpsPathUtil.urlToPath(rootUrl))
|
||||
if (!rootDir.isDirectory) return null
|
||||
|
||||
val file = File(rootDir, "icon-robots.txt")
|
||||
if (!file.exists()) return null
|
||||
|
||||
val prefix = "name:"
|
||||
var moduleName: String? = null
|
||||
file.forEachLine {
|
||||
if (it.startsWith(prefix)) {
|
||||
val name = it.substring(prefix.length).trim()
|
||||
if (name.isNotEmpty()) moduleName = name
|
||||
}
|
||||
}
|
||||
return moduleName
|
||||
}
|
||||
|
||||
private fun getFirstContentRootUrl(module: JpsModule): String? {
|
||||
return module.contentRootsList.urls.firstOrNull()
|
||||
}
|
||||
|
||||
private fun className(name: String): String {
|
||||
val answer = StringBuilder()
|
||||
name.split("-", "_").forEach {
|
||||
answer.append(capitalize(it))
|
||||
}
|
||||
return toJavaIdentifier(answer.toString())
|
||||
}
|
||||
|
||||
private fun iconName(name: String): String {
|
||||
val id = capitalize(name.substring(0, name.lastIndexOf('.')))
|
||||
return toJavaIdentifier(id)
|
||||
}
|
||||
|
||||
private fun toJavaIdentifier(id: String): String {
|
||||
val sb = StringBuilder()
|
||||
id.forEach {
|
||||
if (Character.isJavaIdentifierPart(it)) {
|
||||
sb.append(it)
|
||||
}
|
||||
else {
|
||||
sb.append('_')
|
||||
}
|
||||
}
|
||||
|
||||
if (Character.isJavaIdentifierStart(sb.first())) {
|
||||
return sb.toString()
|
||||
}
|
||||
else {
|
||||
return "_" + sb.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun capitalize(name: String): String {
|
||||
if (name.length == 2) return name.toUpperCase()
|
||||
return name.capitalize()
|
||||
}
|
||||
|
||||
// legacy ordering
|
||||
private val NAME_COMPARATOR: Comparator<String> = Comparator.comparing(Function<String, String> { it.toLowerCase() + "." })
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright 2000-2016 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.build.scripts
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.util.io.FileUtilRt
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes
|
||||
import org.jetbrains.jps.model.module.JpsModule
|
||||
import org.jetbrains.jps.model.module.JpsModuleSourceRoot
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
|
||||
internal class ImagePaths(val id: String, val sourceRoot: JpsModuleSourceRoot, val used: Boolean, val deprecated: Boolean) {
|
||||
var files: MutableMap<ImageType, File> = HashMap()
|
||||
var ambiguous: Boolean = false
|
||||
|
||||
val file: File? get() = files[ImageType.BASIC]
|
||||
val presentablePath: File get() = file ?: files.values.first() ?: File("<unknown>")
|
||||
}
|
||||
|
||||
internal class ImageCollector(val projectHome: File, val iconsOnly: Boolean = true, val ignoreSkipTag: Boolean = false) {
|
||||
private val result = HashMap <String, ImagePaths>()
|
||||
|
||||
private val usedIconsRobots: MutableSet<File> = HashSet()
|
||||
|
||||
fun collect(module: JpsModule): List<ImagePaths> {
|
||||
module.sourceRoots.forEach {
|
||||
processRoot(it)
|
||||
}
|
||||
return result.values.toList()
|
||||
}
|
||||
|
||||
fun printUsedIconRobots() {
|
||||
usedIconsRobots.forEach {
|
||||
println("Found icon-robots: $it")
|
||||
}
|
||||
}
|
||||
|
||||
private fun processRoot(sourceRoot: JpsModuleSourceRoot) {
|
||||
val root = sourceRoot.file
|
||||
if (!root.exists()) return
|
||||
if (!JavaModuleSourceRootTypes.PRODUCTION.contains(sourceRoot.rootType)) return
|
||||
|
||||
val iconsRoot = downToRoot(root)
|
||||
if (iconsRoot == null) return
|
||||
|
||||
val rootRobotData = upToProjectHome(root)
|
||||
if (rootRobotData.isSkipped(root)) return
|
||||
|
||||
val robotData = rootRobotData.fork(iconsRoot, root)
|
||||
|
||||
processDirectory(iconsRoot, sourceRoot, robotData, emptyList<String>())
|
||||
}
|
||||
|
||||
private fun processDirectory(dir: File, sourceRoot: JpsModuleSourceRoot, robotData: IconRobotsData, prefix: List<String>) {
|
||||
dir.children.forEach { file ->
|
||||
if (file.isDirectory) {
|
||||
val root = sourceRoot.file
|
||||
val childRobotData = robotData.fork(file, root)
|
||||
val childPrefix = ContainerUtil.append(prefix, file.name)
|
||||
processDirectory(file, sourceRoot, childRobotData, childPrefix)
|
||||
}
|
||||
else if (isImage(file, iconsOnly)) {
|
||||
processImageFile(file, sourceRoot, robotData, prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processImageFile(file: File, sourceRoot: JpsModuleSourceRoot, robotData: IconRobotsData, prefix: List<String>) {
|
||||
val nameWithoutExtension = FileUtil.getNameWithoutExtension(file.name)
|
||||
val type = ImageType.fromName(nameWithoutExtension)
|
||||
val id = type.getBasicName((prefix + nameWithoutExtension).joinToString("/"))
|
||||
|
||||
val skipped = robotData.isSkipped(file)
|
||||
val used = robotData.isUsed(file)
|
||||
val deprecated = robotData.isDeprecated(file)
|
||||
if (skipped) return
|
||||
|
||||
val iconPaths = result.computeIfAbsent(id, { ImagePaths(id, sourceRoot, used, deprecated) })
|
||||
if (iconPaths.files[type] == null) {
|
||||
iconPaths.files[type] = file
|
||||
}
|
||||
else {
|
||||
iconPaths.ambiguous = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun upToProjectHome(dir: File): IconRobotsData {
|
||||
if (dir == projectHome) return IconRobotsData()
|
||||
val parent = dir.parentFile ?: return IconRobotsData()
|
||||
return upToProjectHome(parent).fork(parent, projectHome)
|
||||
}
|
||||
|
||||
private fun downToRoot(dir: File): File? {
|
||||
val answer = downToRoot(dir, dir, null, IconRobotsData())
|
||||
return if (answer == null || answer.isDirectory) answer else answer.parentFile
|
||||
}
|
||||
|
||||
private fun downToRoot(root: File, file: File, common: File?, robotData: IconRobotsData): File? {
|
||||
if (robotData.isSkipped(file)) return common
|
||||
|
||||
if (file.isDirectory) {
|
||||
val childRobotData = robotData.fork(file, root)
|
||||
|
||||
var childCommon = common
|
||||
file.children.forEach {
|
||||
childCommon = downToRoot(root, it, childCommon, childRobotData)
|
||||
}
|
||||
return childCommon
|
||||
}
|
||||
else if (isImage(file, iconsOnly)) {
|
||||
return getCommonAncestor(common, file)
|
||||
}
|
||||
else {
|
||||
return common
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCommonAncestor(file1: File?, file2: File?): File? {
|
||||
if (file1 == null) return file2
|
||||
if (file2 == null) return file1
|
||||
|
||||
val c1 = pathComponents(file1)
|
||||
val c2 = pathComponents(file2)
|
||||
|
||||
val size = Math.min(c1.size, c2.size)
|
||||
var cur: File? = null
|
||||
for (i in 0..size - 1) {
|
||||
if (c1[i] != c2[i]) break
|
||||
cur = c1[i]
|
||||
}
|
||||
return cur
|
||||
}
|
||||
|
||||
private fun pathComponents(file: File): List<File> {
|
||||
val answer = ArrayList<File>()
|
||||
var cur: File? = file
|
||||
while (cur != null) {
|
||||
answer.add(cur)
|
||||
cur = cur.parentFile
|
||||
}
|
||||
return answer.reversed()
|
||||
}
|
||||
|
||||
private inner class IconRobotsData(private val parent: IconRobotsData? = null) {
|
||||
private val skip: MutableSet<Matcher> = HashSet()
|
||||
private val used: MutableSet<Matcher> = HashSet()
|
||||
private val deprecated: MutableSet<Matcher> = HashSet()
|
||||
|
||||
fun isSkipped(file: File): Boolean = !ignoreSkipTag && (matches(file, skip) || parent?.isSkipped(file) ?: false)
|
||||
fun isUsed(file: File): Boolean = matches(file, used) || parent?.isUsed(file) ?: false
|
||||
fun isDeprecated(file: File): Boolean = matches(file, deprecated) || parent?.isDeprecated(file) ?: false
|
||||
|
||||
fun fork(dir: File, root: File): IconRobotsData {
|
||||
val robots = File(dir, "icon-robots.txt")
|
||||
if (!robots.exists()) return this
|
||||
|
||||
usedIconsRobots.add(robots)
|
||||
|
||||
val answer = IconRobotsData(this)
|
||||
parse(robots,
|
||||
Pair("skip:", { value -> compilePattern(answer.skip, dir, root, value) }),
|
||||
Pair("used:", { value -> compilePattern(answer.used, dir, root, value) }),
|
||||
Pair("deprecated:", { value -> compilePattern(answer.deprecated, dir, root, value) }),
|
||||
Pair("name:", { value -> }), // ignore
|
||||
Pair("#", { value -> }) // comment
|
||||
)
|
||||
return answer
|
||||
}
|
||||
|
||||
private fun parse(robots: File, vararg handlers: Pair<String, (String) -> Unit>) {
|
||||
robots.forEachLine { line ->
|
||||
if (line.isBlank()) return@forEachLine
|
||||
for (h in handlers) {
|
||||
if (line.startsWith(h.first)) {
|
||||
h.second(StringUtil.trimStart(line, h.first))
|
||||
return@forEachLine
|
||||
}
|
||||
}
|
||||
throw Exception("Can't parse $robots. Line: $line")
|
||||
}
|
||||
}
|
||||
|
||||
private fun compilePattern(set: MutableSet<Matcher>, dir: File, root: File, value: String) {
|
||||
var pattern = value.trim()
|
||||
|
||||
if (pattern.startsWith("/")) {
|
||||
pattern = root.absolutePath + pattern
|
||||
}
|
||||
else {
|
||||
pattern = dir.absolutePath + '/' + pattern
|
||||
}
|
||||
|
||||
val regExp = FileUtil.convertAntToRegexp(pattern, false)
|
||||
try {
|
||||
set.add(Pattern.compile(regExp).matcher(""))
|
||||
}
|
||||
catch (e: Exception) {
|
||||
throw Exception("Cannot compile pattern: $pattern. Built on based in $dir/icon-robots.txt")
|
||||
}
|
||||
}
|
||||
|
||||
private fun matches(file: File, matcher: Set<Matcher>): Boolean {
|
||||
val path = file.absolutePath.replace('\\', '/')
|
||||
|
||||
val pathWithoutExtension = FileUtilRt.getNameWithoutExtension(path)
|
||||
val extension = FileUtilRt.getExtension(path)
|
||||
|
||||
val basicPathWithoutExtension = ImageType.stripSuffix(pathWithoutExtension)
|
||||
val basicPath = basicPathWithoutExtension + if (extension.isNotEmpty()) "." + extension else ""
|
||||
|
||||
return matcher.any { it.reset(basicPath).matches() }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2000-2016 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.build.scripts
|
||||
|
||||
import com.intellij.build.scripts.ImageSanityCheckerBase.Severity.*
|
||||
import com.intellij.build.scripts.ImageType.*
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import org.jetbrains.jps.model.module.JpsModule
|
||||
import java.awt.Dimension
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
abstract class ImageSanityCheckerBase(val projectHome: File, val ignoreSkipTag: Boolean) {
|
||||
fun check(module: JpsModule) {
|
||||
val allImages = ImageCollector(projectHome, false, ignoreSkipTag).collect(module)
|
||||
|
||||
val (images, broken) = allImages.partition { it.file != null }
|
||||
log(Severity.ERROR, "image without base version", module, broken)
|
||||
|
||||
checkHaveRetinaVersion(images, module)
|
||||
checkHaveCompleteIconSet(images, module)
|
||||
checkHaveValidSize(images, module)
|
||||
checkAreNotAmbiguous(images, module)
|
||||
}
|
||||
|
||||
private fun checkHaveRetinaVersion(images: List<ImagePaths>, module: JpsModule) {
|
||||
process(images, Severity.INFO, "image without retina version", module) { image ->
|
||||
val hasRetina = image.files[RETINA] != null
|
||||
val hasRetinaDarcula = image.files[RETINA_DARCULA] != null
|
||||
return@process hasRetina || hasRetinaDarcula
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkHaveCompleteIconSet(images: List<ImagePaths>, module: JpsModule) {
|
||||
process(images, WARNING, "image without complete set of additional icons", module) { image ->
|
||||
val hasRetina = image.files[RETINA] != null
|
||||
val hasDarcula = image.files[DARCULA] != null
|
||||
val hasRetinaDarcula = image.files[RETINA_DARCULA] != null
|
||||
|
||||
if (hasRetinaDarcula) {
|
||||
return@process hasRetina && hasDarcula
|
||||
}
|
||||
else {
|
||||
return@process !hasRetina || !hasDarcula
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkHaveValidSize(images: List<ImagePaths>, module: JpsModule) {
|
||||
val excludedPaths = arrayOf(
|
||||
"/tips/images/",
|
||||
"/ide/ui/laf/icons/"
|
||||
)
|
||||
|
||||
process(images, WARNING, "icon with suspicious size", module) { image ->
|
||||
if (!isIcon(image.file!!)) return@process true
|
||||
val path = FileUtil.normalize(image.file!!.path)
|
||||
if (excludedPaths.any { path.contains(it) }) return@process true
|
||||
val sizes = image.files.mapValues { imageSize(it.value) }
|
||||
|
||||
val sizeBasic = sizes[BASIC]!!
|
||||
val sizeRetina = sizes[RETINA]
|
||||
val sizeDarcula = sizes[DARCULA]
|
||||
val sizeRetinaDarcula = sizes[RETINA_DARCULA]
|
||||
|
||||
val sizeBasicTwice = Dimension(sizeBasic.width * 2, sizeBasic.height * 2)
|
||||
return@process (sizeDarcula == null || sizeBasic == sizeDarcula) &&
|
||||
(sizeRetina == null || sizeRetinaDarcula == null || sizeRetina == sizeRetinaDarcula) &&
|
||||
(sizeRetina == null || sizeBasicTwice == sizeRetina)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkAreNotAmbiguous(images: List<ImagePaths>, module: JpsModule) {
|
||||
process(images, WARNING, "image with ambiguous definition (ex: has both '.png' and '.gif' versions)", module) { image ->
|
||||
return@process !image.ambiguous
|
||||
}
|
||||
}
|
||||
|
||||
private fun process(images: List<ImagePaths>, severity: Severity, message: String, module: JpsModule,
|
||||
processor: (ImagePaths) -> Boolean) {
|
||||
val result = ArrayList<ImagePaths>()
|
||||
images.forEach {
|
||||
if (!processor(it)) result.add(it)
|
||||
}
|
||||
log(severity, message, module, result)
|
||||
}
|
||||
|
||||
internal abstract fun log(severity: Severity, message: String, module: JpsModule, images: Collection<ImagePaths>)
|
||||
|
||||
enum class Severity { INFO, WARNING, ERROR }
|
||||
}
|
||||
|
||||
class ImageSanityChecker(projectHome: File) : ImageSanityCheckerBase(projectHome, false) {
|
||||
private val infos: StringBuilder = StringBuilder()
|
||||
private val warnings: StringBuilder = StringBuilder()
|
||||
|
||||
fun printInfo() {
|
||||
if (infos.isNotEmpty()) {
|
||||
println("")
|
||||
println(infos)
|
||||
}
|
||||
}
|
||||
|
||||
fun printWarnings() {
|
||||
if (warnings.isNotEmpty()) {
|
||||
println("")
|
||||
println(warnings)
|
||||
}
|
||||
}
|
||||
|
||||
override fun log(severity: Severity, message: String, module: JpsModule, images: Collection<ImagePaths>) {
|
||||
val logger = when (severity) {
|
||||
ERROR -> warnings
|
||||
WARNING -> warnings
|
||||
INFO -> infos
|
||||
}
|
||||
val prefix = when (severity) {
|
||||
ERROR -> "ERROR:"
|
||||
WARNING -> "WARNING:"
|
||||
INFO -> "INFO:"
|
||||
}
|
||||
|
||||
if (images.isEmpty()) return
|
||||
logger.append("$prefix $message found in module '${module.name}'\n")
|
||||
images.sortedBy { it.id }.forEach {
|
||||
logger.append(" ${it.id} - ${it.presentablePath.path}\n")
|
||||
}
|
||||
logger.append("\n")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2000-2016 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.build.scripts
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import org.jetbrains.jps.model.module.JpsModule
|
||||
import java.awt.image.BufferedImage
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import javax.imageio.ImageIO
|
||||
|
||||
class ImageSizeOptimizer(val projectHome: File) {
|
||||
private var optimizedTotal: Long = 0
|
||||
|
||||
fun optimizeIcons(module: JpsModule) {
|
||||
val icons = ImageCollector(projectHome).collect(module)
|
||||
icons.forEach {
|
||||
it.files.values.forEach {
|
||||
tryToReduceSize(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun optimizeImages(file: File) {
|
||||
if (file.isDirectory) {
|
||||
file.listFiles().forEach {
|
||||
optimizeImages(it)
|
||||
}
|
||||
}
|
||||
else {
|
||||
tryToReduceSize(file)
|
||||
}
|
||||
}
|
||||
|
||||
fun printStats() {
|
||||
println("")
|
||||
println("PNG size optimization: $optimizedTotal bytes in total")
|
||||
}
|
||||
|
||||
private fun tryToReduceSize(file: File) {
|
||||
val image = optimizeImage(file) ?: return
|
||||
|
||||
if (image.hasOptimumSize) return
|
||||
|
||||
try {
|
||||
FileUtil.writeToFile(file, image.optimizedArray)
|
||||
optimizedTotal += image.sizeBefore - image.sizeAfter
|
||||
}
|
||||
catch (e: IOException) {
|
||||
throw Exception("Cannot optimize " + file.absolutePath)
|
||||
}
|
||||
println("${file.absolutePath} ${image.compressionStats}")
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun optimizeImage(file: File): OptimizedImage? {
|
||||
if (!file.name.endsWith(".png")) return null
|
||||
val image = ImageIO.read(file)
|
||||
if (image == null) {
|
||||
println(file.absolutePath + " loading failed")
|
||||
return null
|
||||
}
|
||||
|
||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||
ImageIO.write(image, "png", byteArrayOutputStream)
|
||||
|
||||
val byteArray = byteArrayOutputStream.toByteArray()
|
||||
return OptimizedImage(file, image, byteArray)
|
||||
}
|
||||
}
|
||||
|
||||
class OptimizedImage(val file: File, val image: BufferedImage, val optimizedArray: ByteArray) {
|
||||
val sizeBefore = file.length()
|
||||
val sizeAfter = optimizedArray.size
|
||||
|
||||
val compressionStats: String get() {
|
||||
val compression = (sizeBefore - sizeAfter) * 100 / sizeBefore
|
||||
return "$compression% optimized ($sizeBefore->$sizeAfter bytes)"
|
||||
}
|
||||
|
||||
val hasOptimumSize: Boolean get() = sizeBefore <= sizeAfter
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2000-2016 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.build.scripts
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import java.awt.*
|
||||
import java.io.File
|
||||
|
||||
internal val File.children: List<File> get() = if (this.isDirectory) this.listFiles().toList() else emptyList()
|
||||
|
||||
internal fun isImage(file: File, iconsOnly: Boolean): Boolean {
|
||||
if (!isImage(file.name)) return false
|
||||
return !iconsOnly || isIcon(file)
|
||||
}
|
||||
|
||||
internal fun isIcon(file: File): Boolean {
|
||||
if (!isImage(file.name)) return false
|
||||
val size = imageSize(file)
|
||||
return size.height == size.width || size.height <= 100 && size.width <= 100
|
||||
}
|
||||
|
||||
private fun isImage(name: String) = name.endsWith(".png") || name.endsWith(".gif")
|
||||
|
||||
internal fun imageSize(file: File): Dimension {
|
||||
val image = loadImage(file)
|
||||
val width = image.getWidth(null)
|
||||
val height = image.getHeight(null)
|
||||
return Dimension(width, height)
|
||||
}
|
||||
|
||||
internal fun loadImage(path: File): Image {
|
||||
val image = Toolkit.getDefaultToolkit().createImage(path.absolutePath)
|
||||
waitForImage(image)
|
||||
return image
|
||||
}
|
||||
|
||||
private fun waitForImage(image: Image?): Boolean {
|
||||
if (image == null) return false
|
||||
if (image.getWidth(null) > 0) return true
|
||||
val mediaTracker = MediaTracker(object : Component() {})
|
||||
mediaTracker.addImage(image, 1)
|
||||
mediaTracker.waitForID(1, 5000)
|
||||
return !mediaTracker.isErrorID(1)
|
||||
}
|
||||
|
||||
internal enum class ImageType(private val suffix: String) {
|
||||
BASIC(""), RETINA("@2x"), DARCULA("_dark"), RETINA_DARCULA("@2x_dark");
|
||||
|
||||
fun getBasicName(name: String): String = StringUtil.trimEnd(name, suffix)
|
||||
|
||||
companion object {
|
||||
fun fromName(name: String): ImageType {
|
||||
if (name.endsWith(RETINA_DARCULA.suffix)) return RETINA_DARCULA
|
||||
if (name.endsWith(RETINA.suffix)) return RETINA
|
||||
if (name.endsWith(DARCULA.suffix)) return DARCULA
|
||||
return BASIC
|
||||
}
|
||||
|
||||
fun stripSuffix(name: String): String {
|
||||
val type = fromName(name)
|
||||
return StringUtil.trimEnd(name, type.suffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2000-2016 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.build.scripts
|
||||
|
||||
import com.intellij.openapi.application.PathManager
|
||||
import org.jetbrains.jps.model.JpsElementFactory
|
||||
import org.jetbrains.jps.model.module.JpsModule
|
||||
import org.jetbrains.jps.model.serialization.JpsModelSerializationDataService
|
||||
import org.jetbrains.jps.model.serialization.JpsProjectLoader
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import org.junit.runners.Parameterized.Parameter
|
||||
import org.junit.runners.Parameterized.Parameters
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.comparisons.compareBy
|
||||
import kotlin.comparisons.thenBy
|
||||
|
||||
class CommunityImageResourcesSanityTest : ImageResourcesTestBase() {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameters(name = "{0}")
|
||||
fun data(): Collection<Array<Any>> {
|
||||
return ImageResourcesTestBase.collectBadIcons(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CommunityImageResourcesOptimumSizeTest : ImageResourcesTestBase() {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameters(name = "{0}")
|
||||
fun data(): Collection<Array<Any>> {
|
||||
return ImageResourcesTestBase.collectIconsWithNonOptimumSize(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore
|
||||
class AllImageResourcesSanityTest : ImageResourcesTestBase() {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameters(name = "{0}")
|
||||
fun data(): Collection<Array<Any>> {
|
||||
return ImageResourcesTestBase.collectBadIcons(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore
|
||||
class AllImageResourcesOptimumSizeTest : ImageResourcesTestBase() {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameters(name = "{0}")
|
||||
fun data(): Collection<Array<Any>> {
|
||||
return ImageResourcesTestBase.collectIconsWithNonOptimumSize(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
abstract class ImageResourcesTestBase {
|
||||
@Parameter(value = 0) lateinit var testName: String
|
||||
@Parameter(value = 1) lateinit var exception: Throwable
|
||||
|
||||
@Test
|
||||
fun test() {
|
||||
throw exception
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun collectBadIcons(ignoreSkipTag: Boolean): List<Array<Any>> {
|
||||
val checker = MySanityChecker(File(PathManager.getHomePath()), ignoreSkipTag)
|
||||
forEachModule {
|
||||
checker.check(it)
|
||||
}
|
||||
return createTestData(checker.failures)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun collectIconsWithNonOptimumSize(ignoreSkipTag: Boolean): List<Array<Any>> {
|
||||
val checker = MyOptimumSizeChecker(File(PathManager.getHomePath()), ignoreSkipTag)
|
||||
forEachModule {
|
||||
checker.checkOptimumSizes(it)
|
||||
}
|
||||
return createTestData(checker.failures)
|
||||
}
|
||||
|
||||
private fun createTestData(failures: Collection<FailedTest>): List<Array<Any>> {
|
||||
return failures
|
||||
.sortedWith(compareBy<FailedTest> { it.module }.thenBy { it.id }.thenBy { it.message })
|
||||
.map { arrayOf<Any>(it.getTestName(), it.getException()) }
|
||||
}
|
||||
|
||||
private fun forEachModule(action: (JpsModule) -> Unit) {
|
||||
val home = PathManager.getHomePath()
|
||||
val model = JpsElementFactory.getInstance().createModel()
|
||||
|
||||
val pathVariables = JpsModelSerializationDataService.computeAllPathVariables(model.global)
|
||||
JpsProjectLoader.loadProject(model.project, pathVariables, home)
|
||||
|
||||
model.project.modules.forEach(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MySanityChecker(projectHome: File, ignoreSkipTag: Boolean) : ImageSanityCheckerBase(projectHome, ignoreSkipTag) {
|
||||
val failures = ArrayList<FailedTest>()
|
||||
|
||||
override fun log(severity: ImageSanityCheckerBase.Severity,
|
||||
message: String,
|
||||
module: JpsModule,
|
||||
images: Collection<ImagePaths>) {
|
||||
if (severity == Severity.INFO) return
|
||||
images.forEach { image ->
|
||||
failures.add(FailedTest(module, message, image))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MyOptimumSizeChecker(val projectHome: File, val ignoreSkipTag: Boolean) {
|
||||
val failures = ArrayList<FailedTest>()
|
||||
|
||||
fun checkOptimumSizes(module: JpsModule) {
|
||||
val allImages = ImageCollector(projectHome, false, ignoreSkipTag).collect(module)
|
||||
val images = allImages.filter { it.file != null }
|
||||
|
||||
images.forEach { image ->
|
||||
image.files.values.forEach { file ->
|
||||
val optimized = ImageSizeOptimizer.optimizeImage(file)
|
||||
if (optimized != null && !optimized.hasOptimumSize) {
|
||||
failures.add(FailedTest(module, "image size can be optimized: ${optimized.compressionStats}", image, file))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FailedTest(val module: String, val message: String, val id: String, val path: String) {
|
||||
internal constructor(module: JpsModule, message: String, image: ImagePaths, file: File = image.presentablePath) :
|
||||
this(module.name, message, image.id, file.absolutePath)
|
||||
|
||||
fun getTestName(): String = "'${module}' - $id - $message"
|
||||
fun getException(): Throwable = Exception("${message} - ${path}")
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/groovy" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/icons/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/icons/tests" isTestSource="true" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
@@ -32,5 +34,7 @@
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="JUnit4" level="project"/>
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2014 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.
|
||||
@@ -154,7 +154,6 @@ public class EmptyProgressIndicator implements StandardProgressIndicator {
|
||||
if (indicator != null) {
|
||||
return indicator;
|
||||
}
|
||||
LOG.info("No progress indicator");
|
||||
return new EmptyProgressIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
@@ -525,7 +525,7 @@ public abstract class AbstractColorsScheme implements EditorColorsScheme, Serial
|
||||
if (!isDefault) {
|
||||
size = (fontScale != null) ? size / fontScale : DEFAULT_FONT_SIZE.getSize();
|
||||
}
|
||||
return JBUI.scaleFontSize(size);
|
||||
return (int)JBUI.scale(size);
|
||||
}
|
||||
|
||||
private void readFontSettings(@NotNull Element element,
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 701 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 354 B After Width: | Height: | Size: 219 B |
|
Before Width: | Height: | Size: 579 B After Width: | Height: | Size: 363 B |
@@ -154,6 +154,10 @@ class FTManager {
|
||||
final FileTemplateBase template = getTemplates().get(qName);
|
||||
if (template instanceof CustomFileTemplate) {
|
||||
getTemplates().remove(qName);
|
||||
File file = new File(getConfigRoot(false), qName);
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
mySortedTemplates = null;
|
||||
}
|
||||
else if (template instanceof BundledFileTemplate){
|
||||
@@ -176,6 +180,7 @@ class FTManager {
|
||||
_template.setReformatCode(template.isReformatCode());
|
||||
_template.setLiveTemplateEnabled(template.isLiveTemplateEnabled());
|
||||
}
|
||||
saveTemplates(true);
|
||||
}
|
||||
|
||||
private void restoreDefaults(Set<String> toDisable) {
|
||||
@@ -257,6 +262,10 @@ class FTManager {
|
||||
}
|
||||
|
||||
public void saveTemplates() {
|
||||
saveTemplates(false);
|
||||
}
|
||||
|
||||
private void saveTemplates(boolean removeDeleted) {
|
||||
final File configRoot = getConfigRoot(true);
|
||||
|
||||
final File[] files = configRoot.listFiles();
|
||||
@@ -300,7 +309,9 @@ class FTManager {
|
||||
}
|
||||
else if (templateToSave == null) {
|
||||
// template was removed
|
||||
FileUtil.delete(customizedTemplateFile);
|
||||
if (removeDeleted) {
|
||||
FileUtil.delete(customizedTemplateFile);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// both customized content on disk and corresponding template are present
|
||||
|
||||
@@ -391,7 +391,6 @@ public class FileTemplateManagerImpl extends FileTemplateManager implements Pers
|
||||
for (FTManager manager : getAllManagers()) {
|
||||
if (templatesCategory.equals(manager.getName())) {
|
||||
manager.updateTemplates(templates);
|
||||
manager.saveTemplates();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Vladimir Kondratyev
|
||||
@@ -615,7 +617,8 @@ public class ThreeComponentsSplitter extends JPanel implements Disposable {
|
||||
if (window != null) {
|
||||
Point point = SwingUtilities.convertPoint(this, p, window);
|
||||
Component component = SwingUtilities.getDeepestComponentAt(window, point.x, point.y);
|
||||
if (UIUtil.findParentByCondition(component, c -> c == ThreeComponentsSplitter.this) == null) return false;
|
||||
List<Component> components = Arrays.asList(myFirstComponent, myFirstDivider, myInnerComponent, myLastDivider, myLastComponent);
|
||||
if (UIUtil.findParentByCondition(component, c -> c != null && components.contains(c)) == null) return false;
|
||||
}
|
||||
|
||||
int dndOff = myIsOnePixel ? JBUI.scale(Registry.intValue("ide.splitter.mouseZone")) / 2 : 0;
|
||||
|
||||
@@ -31,7 +31,9 @@ import com.intellij.util.ArrayUtil;
|
||||
import org.jdom.Element;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Dmitry Avdeev
|
||||
@@ -111,7 +113,7 @@ public class LightFileTemplatesTest extends LightPlatformTestCase {
|
||||
public void testSurviveOnProjectReopen() throws Exception {
|
||||
File foo = PlatformTestCase.createTempDir("foo");
|
||||
Project reloaded = null;
|
||||
final Project project = ProjectManager.getInstance().createProject("foo", foo.getPath());;
|
||||
final Project project = ProjectManager.getInstance().createProject("foo", foo.getPath());
|
||||
try {
|
||||
assertNotNull(project);
|
||||
FileTemplateManager manager = FileTemplateManager.getInstance(project);
|
||||
@@ -139,6 +141,46 @@ public class LightFileTemplatesTest extends LightPlatformTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testAddRemoveShared() throws Exception {
|
||||
File foo = PlatformTestCase.createTempDir("foo");
|
||||
final Project project = ProjectManager.getInstance().createProject("foo", foo.getPath());;
|
||||
try {
|
||||
assertNotNull(project);
|
||||
FileTemplateManager manager = FileTemplateManager.getInstance(project);
|
||||
manager.setCurrentScheme(manager.getProjectScheme());
|
||||
manager.saveAllTemplates();
|
||||
|
||||
FileTemplateSettings settings = ServiceManager.getService(project, FileTemplateSettings.class);
|
||||
FTManager ftManager = settings.getDefaultTemplatesManager();
|
||||
File root = ftManager.getConfigRoot(false);
|
||||
assertTrue(root.exists());
|
||||
File file = new File(root, "Foo.java");
|
||||
assertTrue(file.createNewFile());
|
||||
manager.saveAllTemplates();
|
||||
assertTrue(file.exists());
|
||||
|
||||
FileTemplate template = manager.addTemplate("Foo", "java");
|
||||
// now remove it via "remove template" call
|
||||
manager.removeTemplate(template);
|
||||
manager.saveAllTemplates();
|
||||
assertFalse(file.exists());
|
||||
|
||||
// check "setTemplates" call
|
||||
FileTemplateBase templateBase = (FileTemplateBase)manager.addTemplate("Foo", "java");
|
||||
List<FileTemplate> templates = new ArrayList<>(ftManager.getAllTemplates(true));
|
||||
assertTrue(templates.contains(templateBase));
|
||||
ftManager.saveTemplates();
|
||||
assertTrue(file.exists());
|
||||
|
||||
templates.remove(templateBase);
|
||||
manager.setTemplates(FileTemplateManager.DEFAULT_TEMPLATES_CATEGORY, templates);
|
||||
assertFalse(file.exists());
|
||||
}
|
||||
finally {
|
||||
closeProject(project);
|
||||
}
|
||||
}
|
||||
|
||||
private static void closeProject(final Project project) {
|
||||
if (project != null && !project.isDisposed()) {
|
||||
ProjectManager.getInstance().closeProject(project);
|
||||
|
||||
@@ -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.
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.intellij.ui;
|
||||
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.ui.paint.RectanglePainter;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -70,7 +71,7 @@ public interface PopupBorder extends Border {
|
||||
|
||||
Color color = myActive ? myActiveColor : myPassiveColor;
|
||||
g.setColor(color);
|
||||
g.drawRect(x, y, width - 1, height - 1);
|
||||
RectanglePainter.DRAW.paint((Graphics2D)g, x, y, width, height, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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.
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.
|
||||
@@ -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.
|
||||
@@ -27,6 +27,7 @@ import com.intellij.psi.scope.PsiScopeProcessor;
|
||||
import com.intellij.ui.RowIcon;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.PathUtil;
|
||||
import icons.JetgroovyIcons;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -231,8 +232,7 @@ public class GroovyScriptClass extends GrLightTypeDefinitionBase implements Synt
|
||||
@Override
|
||||
@Nullable
|
||||
public Icon getIcon(int flags) {
|
||||
final Icon icon = myFile.getIcon(flags);
|
||||
RowIcon baseIcon = ElementBase.createLayeredIcon(this, icon, 0);
|
||||
RowIcon baseIcon = ElementBase.createLayeredIcon(this, JetgroovyIcons.Groovy.Class, 0);
|
||||
return ElementPresentationUtil.addVisibilityIcon(this, flags, baseIcon);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ public class XpathIcons {
|
||||
public static final Icon Namespace = load("/icons/namespace.png"); // 21x21
|
||||
public static final Icon Tag = load("/icons/tag.png"); // 16x16
|
||||
public static final Icon Template = load("/icons/template.png"); // 18x18
|
||||
public static final Icon Xml = load("/icons/xml.png"); // 16x16
|
||||
public static final Icon Xpath = load("/icons/xpath.png"); // 12x16
|
||||
public static final Icon Xslt_filetype_overlay = load("/icons/xslt-filetype-overlay.png"); // 11x16
|
||||
public static final Icon Xslt = load("/icons/xslt.png"); // 16x16
|
||||
public static final Icon Xml = load("/icons/xml.png"); // 16x16
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -28,6 +28,7 @@
|
||||
<lang.formatter language="Python" implementationClass="com.jetbrains.python.formatter.PythonFormattingModelBuilder"/>
|
||||
<preFormatProcessor implementation="com.jetbrains.python.formatter.PyPreFormatProcessor"/>
|
||||
<postFormatProcessor implementation="com.jetbrains.python.formatter.PyTrailingBlankLinesPostFormatProcessor"/>
|
||||
<postFormatProcessor implementation="com.jetbrains.python.formatter.PyFromImportPostFormatProcessor"/>
|
||||
<lang.whiteSpaceFormattingStrategy language="Python"
|
||||
implementationClass="com.jetbrains.python.formatter.PyWhiteSpaceFormattingStrategy"/>
|
||||
<lang.lineWrapStrategy language="Python" implementationClass="com.jetbrains.python.formatter.PyLineWrapPositionStrategy"/>
|
||||
|
||||
@@ -1033,7 +1033,9 @@ formatter.multi.clause.statements=Multi-clause statements
|
||||
formatter.force.new.line.after.colon=Force new line after colon
|
||||
formatter.align.when.multiline=Align when multiline
|
||||
formatter.collections.and.comprehensions=Collections and Comprehensions
|
||||
formatter.import.statements=Import Statements
|
||||
formatter.from.import.statements="From" Import Statements
|
||||
formatter.from.import.statements.force.parentheses.if.multiline=Force parentheses if multiline
|
||||
formatter.from.import.statements.force.comma.if.multline=Force trailing comma if multiline
|
||||
formatter.dictionary.literals=Dictionary literals
|
||||
formatter.hang.closing.brackets=Hang closing brackets
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
|
||||
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
|
||||
import com.intellij.psi.impl.source.tree.TreeUtil;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
@@ -90,10 +89,13 @@ public class PyBlock implements ASTBlock {
|
||||
private final PyBlockContext myContext;
|
||||
private List<PyBlock> mySubBlocks = null;
|
||||
private Map<ASTNode, PyBlock> mySubBlockByNode = null;
|
||||
private Alignment myChildAlignment;
|
||||
private final Alignment myDictAlignment;
|
||||
private final Wrap myDictWrapping;
|
||||
private final boolean myEmptySequence;
|
||||
|
||||
// Shared among multiple children sub-blocks
|
||||
private Alignment myChildAlignment = null;
|
||||
private Alignment myDictAlignment = null;
|
||||
private Wrap myDictWrapping = null;
|
||||
private Wrap myFromImportWrapping = null;
|
||||
|
||||
public PyBlock(@Nullable PyBlock parent,
|
||||
@NotNull ASTNode node,
|
||||
@@ -109,13 +111,13 @@ public class PyBlock implements ASTBlock {
|
||||
myContext = context;
|
||||
myEmptySequence = isEmptySequence(node);
|
||||
|
||||
final PyCodeStyleSettings pySettings = myContext.getPySettings();
|
||||
if (node.getElementType() == PyElementTypes.DICT_LITERAL_EXPRESSION) {
|
||||
myDictAlignment = Alignment.createAlignment(true);
|
||||
myDictWrapping = Wrap.createWrap(myContext.getPySettings().DICT_WRAPPING, true);
|
||||
myDictWrapping = Wrap.createWrap(pySettings.DICT_WRAPPING, true);
|
||||
}
|
||||
else {
|
||||
myDictAlignment = null;
|
||||
myDictWrapping = null;
|
||||
else if (node.getElementType() == PyElementTypes.FROM_IMPORT_STATEMENT) {
|
||||
myFromImportWrapping = Wrap.createWrap(pySettings.FROM_IMPORT_WRAPPING, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,9 +186,11 @@ public class PyBlock implements ASTBlock {
|
||||
final IElementType grandparentType = grandParentNode == null ? null : grandParentNode.getElementType();
|
||||
|
||||
final IElementType childType = child.getElementType();
|
||||
Wrap wrap = null;
|
||||
Wrap childWrap = null;
|
||||
Indent childIndent = Indent.getNoneIndent();
|
||||
Alignment childAlignment = null;
|
||||
|
||||
final PyCodeStyleSettings settings = myContext.getPySettings();
|
||||
|
||||
if (parentType == PyElementTypes.BINARY_EXPRESSION && !isInControlStatement()) {
|
||||
//Setup alignments for binary expression
|
||||
@@ -220,7 +224,12 @@ public class PyBlock implements ASTBlock {
|
||||
}
|
||||
}
|
||||
else if (childType == PyElementTypes.IMPORT_ELEMENT) {
|
||||
wrap = Wrap.createWrap(WrapType.NORMAL, true);
|
||||
if (parentType == PyElementTypes.FROM_IMPORT_STATEMENT) {
|
||||
childWrap = myFromImportWrapping;
|
||||
}
|
||||
else {
|
||||
childWrap = Wrap.createWrap(WrapType.NORMAL, true);
|
||||
}
|
||||
childIndent = Indent.getNormalIndent();
|
||||
}
|
||||
if (childType == PyTokenTypes.END_OF_LINE_COMMENT && parentType == PyElementTypes.FROM_IMPORT_STATEMENT) {
|
||||
@@ -232,7 +241,7 @@ public class PyBlock implements ASTBlock {
|
||||
!ourBrackets.contains(childType) &&
|
||||
childType != PyTokenTypes.COMMA &&
|
||||
!isSliceOperand(child) /*&& !isSubscriptionOperand(child)*/) {
|
||||
wrap = Wrap.createWrap(WrapType.NORMAL, true);
|
||||
childWrap = Wrap.createWrap(WrapType.NORMAL, true);
|
||||
}
|
||||
if (needListAlignment(child) && !myEmptySequence) {
|
||||
childAlignment = getAlignmentForChildren();
|
||||
@@ -251,7 +260,7 @@ public class PyBlock implements ASTBlock {
|
||||
}
|
||||
}
|
||||
|
||||
final PyCodeStyleSettings settings = CodeStyleSettingsManager.getSettings(child.getPsi().getProject()).getCustomSettings(PyCodeStyleSettings.class);
|
||||
|
||||
if (parentType == PyElementTypes.LIST_LITERAL_EXPRESSION || parentType == PyElementTypes.LIST_COMP_EXPRESSION) {
|
||||
if ((childType == PyTokenTypes.RBRACKET && !settings.HANG_CLOSING_BRACKETS) || childType == PyTokenTypes.LBRACKET) {
|
||||
childIndent = Indent.getNoneIndent();
|
||||
@@ -286,7 +295,8 @@ public class PyBlock implements ASTBlock {
|
||||
}
|
||||
if (childType == PyTokenTypes.RPAR) {
|
||||
childIndent = Indent.getNoneIndent();
|
||||
if (!hasHangingIndent(myNode.getPsi())) {
|
||||
// Don't have hanging indent and is not going to have it due to the setting about opening parenthesis
|
||||
if (!hasHangingIndent(myNode.getPsi()) && !settings.FROM_IMPORT_NEW_LINE_AFTER_LEFT_PARENTHESIS) {
|
||||
childAlignment = getAlignmentForChildren();
|
||||
}
|
||||
else if (settings.HANG_CLOSING_BRACKETS) {
|
||||
@@ -370,7 +380,7 @@ public class PyBlock implements ASTBlock {
|
||||
}
|
||||
}
|
||||
if (childType == PyElementTypes.KEY_VALUE_EXPRESSION && isChildOfDictLiteral(child)) {
|
||||
wrap = myDictWrapping;
|
||||
childWrap = myDictWrapping;
|
||||
childIndent = Indent.getNormalIndent();
|
||||
}
|
||||
|
||||
@@ -406,7 +416,7 @@ public class PyBlock implements ASTBlock {
|
||||
prev = prev.getTreePrev();
|
||||
}
|
||||
|
||||
return new PyBlock(this, child, childAlignment, childIndent, wrap, myContext);
|
||||
return new PyBlock(this, child, childAlignment, childIndent, childWrap, myContext);
|
||||
}
|
||||
|
||||
private static boolean isValueOfKeyValuePairOfDictLiteral(@NotNull ASTNode node) {
|
||||
@@ -607,7 +617,8 @@ public class PyBlock implements ASTBlock {
|
||||
myContext.getMode() == FormattingMode.ADJUST_INDENT) {
|
||||
return true;
|
||||
}
|
||||
return !hasHangingIndent(myNode.getPsi());
|
||||
return !hasHangingIndent(myNode.getPsi()) && !(myNode.getElementType() == PyElementTypes.DICT_LITERAL_EXPRESSION &&
|
||||
myContext.getPySettings().DICT_NEW_LINE_AFTER_LEFT_BRACE);
|
||||
}
|
||||
if (myNode.getElementType() == PyElementTypes.ARGUMENT_LIST) {
|
||||
if (!myContext.getSettings().ALIGN_MULTILINE_PARAMETERS_IN_CALLS || hasHangingIndent(myNode.getPsi())) {
|
||||
|
||||
@@ -17,8 +17,10 @@ package com.jetbrains.python.formatter;
|
||||
|
||||
import com.intellij.formatting.WrapType;
|
||||
import com.intellij.psi.codeStyle.CodeStyleSettings;
|
||||
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
|
||||
import com.intellij.psi.codeStyle.CustomCodeStyleSettings;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import org.intellij.lang.annotations.MagicConstant;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
@@ -74,6 +76,12 @@ public class PyCodeStyleSettings extends CustomCodeStyleSettings {
|
||||
public boolean SPACE_BEFORE_NUMBER_SIGN = true;
|
||||
|
||||
public int DICT_ALIGNMENT = DICT_ALIGNMENT_NONE;
|
||||
@MagicConstant(intValues = {
|
||||
CommonCodeStyleSettings.DO_NOT_WRAP,
|
||||
CommonCodeStyleSettings.WRAP_AS_NEEDED,
|
||||
CommonCodeStyleSettings.WRAP_ALWAYS,
|
||||
CommonCodeStyleSettings.WRAP_ON_EVERY_ITEM
|
||||
})
|
||||
public int DICT_WRAPPING = WrapType.NORMAL.getLegacyRepresentation();
|
||||
public boolean DICT_NEW_LINE_AFTER_LEFT_BRACE = false;
|
||||
public boolean DICT_NEW_LINE_BEFORE_RIGHT_BRACE = false;
|
||||
@@ -93,6 +101,27 @@ public class PyCodeStyleSettings extends CustomCodeStyleSettings {
|
||||
public boolean OPTIMIZE_IMPORTS_SORT_BY_TYPE_FIRST = true;
|
||||
public boolean OPTIMIZE_IMPORTS_JOIN_FROM_IMPORTS_WITH_SAME_SOURCE = false;
|
||||
|
||||
/**
|
||||
* Affects wrapping of multiple imported names in a single "from" import.
|
||||
*/
|
||||
@MagicConstant(intValues = {
|
||||
CommonCodeStyleSettings.DO_NOT_WRAP,
|
||||
CommonCodeStyleSettings.WRAP_AS_NEEDED,
|
||||
CommonCodeStyleSettings.WRAP_ALWAYS,
|
||||
CommonCodeStyleSettings.WRAP_ON_EVERY_ITEM
|
||||
})
|
||||
public int FROM_IMPORT_WRAPPING = WrapType.NORMAL.getLegacyRepresentation();
|
||||
public boolean FROM_IMPORT_NEW_LINE_AFTER_LEFT_PARENTHESIS = false;
|
||||
public boolean FROM_IMPORT_NEW_LINE_BEFORE_RIGHT_PARENTHESIS = false;
|
||||
|
||||
@MagicConstant(intValues = {
|
||||
CommonCodeStyleSettings.DO_NOT_FORCE,
|
||||
CommonCodeStyleSettings.FORCE_BRACES_IF_MULTILINE,
|
||||
CommonCodeStyleSettings.FORCE_BRACES_ALWAYS}
|
||||
)
|
||||
public boolean FROM_IMPORT_PARENTHESES_FORCE_IF_MULTILINE = false;
|
||||
public boolean FROM_IMPORT_TRAILING_COMMA_IF_MULTILINE = false;
|
||||
|
||||
/**
|
||||
* Corresponds to the option of pycodestyle.py "--hang-closing". Basically, it means that the closing brace of a collection literal,
|
||||
* a comprehension, an argument list, a parameter list or parentheses in "from" import statement should have the same indent as the items
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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.jetbrains.python.formatter;
|
||||
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiComment;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiWhiteSpace;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.codeStyle.CodeStyleSettings;
|
||||
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
|
||||
import com.intellij.psi.impl.source.codeStyle.PostFormatProcessor;
|
||||
import com.intellij.psi.impl.source.codeStyle.PostFormatProcessorHelper;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Mikhail Golubev
|
||||
*/
|
||||
public class PyFromImportPostFormatProcessor implements PostFormatProcessor {
|
||||
@Override
|
||||
public PsiElement processElement(@NotNull PsiElement source, @NotNull CodeStyleSettings settings) {
|
||||
return new Visitor(settings).processElement(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextRange processText(@NotNull PsiFile source, @NotNull TextRange rangeToReformat, @NotNull CodeStyleSettings settings) {
|
||||
return new Visitor(settings).processTextRange(source, rangeToReformat);
|
||||
}
|
||||
|
||||
private static class Visitor extends PyRecursiveElementVisitor {
|
||||
private final PostFormatProcessorHelper myHelper;
|
||||
private final List<PyFromImportStatement> myImportStatements = new ArrayList<>();
|
||||
|
||||
public Visitor(@NotNull CommonCodeStyleSettings settings) {
|
||||
myHelper = new PostFormatProcessorHelper(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyFromImportStatement(PyFromImportStatement node) {
|
||||
if (myHelper.isElementFullyInRange(node)) {
|
||||
// If non-parenthesized "from" import ends with one or more of trailing commas, the array returned by getImportElements()
|
||||
// contains empty import elements at the end
|
||||
final List<PyImportElement> importedNames = ContainerUtil.filter(node.getImportElements(), elem -> elem.getTextLength() != 0);
|
||||
if (importedNames.size() > 1) {
|
||||
|
||||
final PyCodeStyleSettings pySettings = ((CodeStyleSettings)myHelper.getSettings()).getCustomSettings(PyCodeStyleSettings.class);
|
||||
final boolean forcedParens = pySettings.FROM_IMPORT_PARENTHESES_FORCE_IF_MULTILINE && PostFormatProcessorHelper.isMultiline(node);
|
||||
final boolean forcedComma = pySettings.FROM_IMPORT_TRAILING_COMMA_IF_MULTILINE && PostFormatProcessorHelper.isMultiline(node);
|
||||
final PyImportElement lastImportedName = importedNames.get(importedNames.size() - 1);
|
||||
final PsiElement afterLastName = PyPsiUtils.getNextNonCommentSibling(lastImportedName, true);
|
||||
final PsiElement openingParen = node.getLeftParen();
|
||||
final boolean missingComma = afterLastName == null || afterLastName.getNode().getElementType() != PyTokenTypes.COMMA;
|
||||
// Trailing comma is allowed only in "from" imports wrapped in parentheses
|
||||
if (forcedParens && openingParen == null || forcedComma && missingComma && openingParen != null) {
|
||||
myImportStatements.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PsiElement processElement(@NotNull PsiElement element) {
|
||||
findAndReplaceFromImports(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public TextRange processTextRange(@NotNull PsiFile file, @NotNull TextRange range) {
|
||||
myHelper.setResultTextRange(range);
|
||||
findAndReplaceFromImports(file);
|
||||
return myHelper.getResultTextRange();
|
||||
}
|
||||
|
||||
private void findAndReplaceFromImports(@NotNull PsiElement element) {
|
||||
if (element.getContainingFile() instanceof PyFile) {
|
||||
element.accept(this);
|
||||
Collections.reverse(myImportStatements);
|
||||
for (PyFromImportStatement statement : myImportStatements) {
|
||||
replaceFromImport(statement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private PyFromImportStatement replaceFromImport(@NotNull PyFromImportStatement fromImport) {
|
||||
final PyImportElement[] allNames = fromImport.getImportElements();
|
||||
final PyImportElement firstName = allNames[0];
|
||||
final PyElementGenerator generator = PyElementGenerator.getInstance(fromImport.getProject());
|
||||
final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(fromImport.getProject());
|
||||
|
||||
if (fromImport.getLeftParen() == null) {
|
||||
// Surround with parentheses stripping obsolete continuation backslashes and added trailing comma if necessary
|
||||
final String beforeFirstName = fromImport.getText().substring(0, firstName.getStartOffsetInParent());
|
||||
final StringBuilder newStatementText = new StringBuilder(beforeFirstName);
|
||||
newStatementText.append("(");
|
||||
boolean lastElementWasComment = false;
|
||||
int lastVisibleNameCommaOffset = -1;
|
||||
for (PsiElement cur = firstName; cur != null; cur = cur.getNextSibling()) {
|
||||
if (cur instanceof PsiWhiteSpace) {
|
||||
newStatementText.append(cur.getText().replace("\\", ""));
|
||||
}
|
||||
else {
|
||||
newStatementText.append(cur.getText());
|
||||
}
|
||||
if (cur instanceof PyImportElement && cur.getTextLength() != 0) {
|
||||
lastVisibleNameCommaOffset = newStatementText.length();
|
||||
}
|
||||
else if (lastVisibleNameCommaOffset != -1 && cur.getNode().getElementType() == PyTokenTypes.COMMA) {
|
||||
lastVisibleNameCommaOffset = -1;
|
||||
}
|
||||
lastElementWasComment = cur instanceof PsiComment;
|
||||
}
|
||||
final PyCodeStyleSettings pySettings = ((CodeStyleSettings)myHelper.getSettings()).getCustomSettings(PyCodeStyleSettings.class);
|
||||
if (lastVisibleNameCommaOffset != -1 && pySettings.FROM_IMPORT_TRAILING_COMMA_IF_MULTILINE) {
|
||||
newStatementText.insert(lastVisibleNameCommaOffset, ",");
|
||||
}
|
||||
if (lastElementWasComment) {
|
||||
newStatementText.append("\n");
|
||||
}
|
||||
newStatementText.append(")");
|
||||
|
||||
final LanguageLevel level = LanguageLevel.forElement(fromImport);
|
||||
PyFromImportStatement newFromImport = generator.createFromText(level, PyFromImportStatement.class, newStatementText.toString());
|
||||
newFromImport = (PyFromImportStatement)fromImport.replace(newFromImport);
|
||||
newFromImport = (PyFromImportStatement)codeStyleManager.reformat(newFromImport, true);
|
||||
myHelper.updateResultRange(fromImport.getTextLength(), newFromImport.getTextLength());
|
||||
return newFromImport;
|
||||
}
|
||||
else {
|
||||
// Add only trailing comma
|
||||
final PsiElement comma = fromImport.addAfter(generator.createComma().getPsi(), allNames[allNames.length - 1]);
|
||||
codeStyleManager.reformat(comma);
|
||||
return fromImport;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -119,9 +119,24 @@ public class PyLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettin
|
||||
consumer.showCustomOption(PyCodeStyleSettings.class, "ALIGN_COLLECTIONS_AND_COMPREHENSIONS",
|
||||
PyBundle.message("formatter.align.when.multiline"),
|
||||
PyBundle.message("formatter.collections.and.comprehensions"));
|
||||
|
||||
consumer.showCustomOption(PyCodeStyleSettings.class, "FROM_IMPORT_WRAPPING",
|
||||
PyBundle.message("formatter.from.import.statements"), null, WRAP_OPTIONS, WRAP_VALUES);
|
||||
consumer.showCustomOption(PyCodeStyleSettings.class, "ALIGN_MULTILINE_IMPORTS",
|
||||
PyBundle.message("formatter.align.when.multiline"),
|
||||
PyBundle.message("formatter.import.statements"));
|
||||
PyBundle.message("formatter.from.import.statements"));
|
||||
consumer.showCustomOption(PyCodeStyleSettings.class, "FROM_IMPORT_NEW_LINE_AFTER_LEFT_PARENTHESIS",
|
||||
ApplicationBundle.message("wrapping.new.line.after.lpar"),
|
||||
PyBundle.message("formatter.from.import.statements"));
|
||||
consumer.showCustomOption(PyCodeStyleSettings.class, "FROM_IMPORT_NEW_LINE_BEFORE_RIGHT_PARENTHESIS",
|
||||
ApplicationBundle.message("wrapping.rpar.on.new.line"),
|
||||
PyBundle.message("formatter.from.import.statements"));
|
||||
consumer.showCustomOption(PyCodeStyleSettings.class, "FROM_IMPORT_PARENTHESES_FORCE_IF_MULTILINE",
|
||||
PyBundle.message("formatter.from.import.statements.force.parentheses.if.multiline"),
|
||||
PyBundle.message("formatter.from.import.statements"));
|
||||
consumer.showCustomOption(PyCodeStyleSettings.class, "FROM_IMPORT_TRAILING_COMMA_IF_MULTILINE",
|
||||
PyBundle.message("formatter.from.import.statements.force.comma.if.multline"),
|
||||
PyBundle.message("formatter.from.import.statements"));
|
||||
|
||||
consumer.showCustomOption(PyCodeStyleSettings.class, "DICT_WRAPPING",
|
||||
PyBundle.message("formatter.dictionary.literals"), null, WRAP_OPTIONS, WRAP_VALUES);
|
||||
@@ -177,17 +192,32 @@ public class PyLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettin
|
||||
" def foo(self):\n" +
|
||||
" pass";
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private static String WRAP_SETTINGS_PREVIEW = "from foo import (bar,\n" +
|
||||
" baz)\n\n" +
|
||||
"long_expression = component_one + component_two + component_three + component_four + component_five + component_six\n\n" +
|
||||
private static String WRAP_SETTINGS_PREVIEW = "from module import foo, bar, baz, quux\n" +
|
||||
"\n" +
|
||||
"long_expression = component_one + component_two + component_three + component_four + component_five + component_six\n" +
|
||||
"\n" +
|
||||
"def xyzzy(long_parameter_1,\n" +
|
||||
"long_parameter_2):\n" +
|
||||
" pass\n\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"xyzzy('long_string_constant1',\n" +
|
||||
" 'long_string_constant2')\n" +
|
||||
"\n" +
|
||||
"xyzzy(\n" +
|
||||
" 'with',\n" +
|
||||
" 'hanging',\n" +
|
||||
" 'indent'\n" +
|
||||
")\n" +
|
||||
"attrs = [e.attr for e in\n" +
|
||||
" items]\n\n" +
|
||||
"if True: pass\n\n" +
|
||||
" items]\n" +
|
||||
"\n" +
|
||||
"ingredients = [\n" +
|
||||
" 'green',\n" +
|
||||
" 'eggs',\n" +
|
||||
"]\n" +
|
||||
"\n" +
|
||||
"if True: pass\n" +
|
||||
"\n" +
|
||||
"try: pass\n" +
|
||||
"finally: pass\n";
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
|
||||
@@ -101,19 +101,23 @@ public class PythonFormattingModelBuilder implements FormattingModelBuilderEx, C
|
||||
.afterInside(RARROW, ANNOTATION).spaces(1)
|
||||
|
||||
.between(allButLambda(), PARAMETER_LIST).spaceIf(commonSettings.SPACE_BEFORE_METHOD_PARENTHESES)
|
||||
|
||||
.betweenInside(COMMA, RBRACE, DICT_LITERAL_EXPRESSION).spaceIf(pySettings.SPACE_WITHIN_BRACES | commonSettings.SPACE_AFTER_COMMA,
|
||||
|
||||
.betweenInside(COMMA, RBRACE, DICT_LITERAL_EXPRESSION).spaceIf(pySettings.SPACE_WITHIN_BRACES | commonSettings.SPACE_AFTER_COMMA,
|
||||
pySettings.DICT_NEW_LINE_BEFORE_RIGHT_BRACE)
|
||||
.afterInside(LBRACE, DICT_LITERAL_EXPRESSION).spaceIf(pySettings.SPACE_WITHIN_BRACES, pySettings.DICT_NEW_LINE_AFTER_LEFT_BRACE)
|
||||
.beforeInside(RBRACE, DICT_LITERAL_EXPRESSION).spaceIf(pySettings.SPACE_WITHIN_BRACES, pySettings.DICT_NEW_LINE_BEFORE_RIGHT_BRACE)
|
||||
|
||||
.between(COMMA, RBRACE).spaceIf(pySettings.SPACE_WITHIN_BRACES | commonSettings.SPACE_AFTER_COMMA)
|
||||
.withinPair(LBRACE, RBRACE).spaceIf(pySettings.SPACE_WITHIN_BRACES)
|
||||
|
||||
|
||||
.between(COMMA, RBRACKET).spaceIf(commonSettings.SPACE_WITHIN_BRACKETS | commonSettings.SPACE_AFTER_COMMA)
|
||||
.withinPair(LBRACKET, RBRACKET).spaceIf(commonSettings.SPACE_WITHIN_BRACKETS)
|
||||
|
||||
|
||||
.before(COLON).spaceIf(pySettings.SPACE_BEFORE_PY_COLON)
|
||||
.afterInside(LPAR, FROM_IMPORT_STATEMENT).spaces(0, pySettings.FROM_IMPORT_NEW_LINE_AFTER_LEFT_PARENTHESIS)
|
||||
.betweenInside(COMMA, RPAR, FROM_IMPORT_STATEMENT).spaceIf(commonSettings.SPACE_AFTER_COMMA,
|
||||
pySettings.FROM_IMPORT_NEW_LINE_BEFORE_RIGHT_PARENTHESIS)
|
||||
.beforeInside(RPAR, FROM_IMPORT_STATEMENT).spaces(0, pySettings.FROM_IMPORT_NEW_LINE_BEFORE_RIGHT_PARENTHESIS)
|
||||
.after(COMMA).spaceIf(commonSettings.SPACE_AFTER_COMMA)
|
||||
.before(COMMA).spaceIf(commonSettings.SPACE_BEFORE_COMMA)
|
||||
.between(FROM_KEYWORD, DOT).spaces(1)
|
||||
@@ -125,7 +129,6 @@ public class PythonFormattingModelBuilder implements FormattingModelBuilderEx, C
|
||||
.withinPairInside(LPAR, RPAR, ARGUMENT_LIST).spaceIf(commonSettings.SPACE_WITHIN_METHOD_CALL_PARENTHESES)
|
||||
.betweenInside(LPAR, RPAR, PARAMETER_LIST).spaceIf(commonSettings.SPACE_WITHIN_EMPTY_METHOD_PARENTHESES)
|
||||
.withinPairInside(LPAR, RPAR, PARAMETER_LIST).spaceIf(commonSettings.SPACE_WITHIN_METHOD_PARENTHESES)
|
||||
.withinPairInside(LPAR, RPAR, FROM_IMPORT_STATEMENT).spaces(0)
|
||||
.withinPairInside(LPAR, RPAR, GENERATOR_EXPRESSION).spaces(0)
|
||||
.withinPairInside(LPAR, RPAR, PARENTHESIZED_EXPRESSION).spaces(0)
|
||||
.before(LBRACKET).spaceIf(pySettings.SPACE_BEFORE_LBRACKET)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
from module import foo
|
||||
from module import foo, bar
|
||||
# | margin
|
||||
from module import foo, bar, baz
|
||||
from module import foo, \
|
||||
bar
|
||||
from module import foo, \
|
||||
bar # comment
|
||||
from module import foo, \
|
||||
\
|
||||
\
|
||||
bar,
|
||||
@@ -0,0 +1,13 @@
|
||||
from module import foo
|
||||
from module import foo, bar
|
||||
# | margin
|
||||
from module import (foo, bar,
|
||||
baz)
|
||||
from module import (foo,
|
||||
bar)
|
||||
from module import (foo,
|
||||
bar # comment
|
||||
)
|
||||
from module import (foo,
|
||||
|
||||
bar, )
|
||||
@@ -0,0 +1,11 @@
|
||||
from module import (foo, bar)
|
||||
from module import (foo, bar,)
|
||||
from module import (foo, bar, baz, quux)
|
||||
from module import (foo,
|
||||
bar)
|
||||
from module import (foo,
|
||||
bar,
|
||||
)
|
||||
from module import (
|
||||
foo,
|
||||
bar)
|
||||
@@ -0,0 +1,11 @@
|
||||
from module import (foo, bar)
|
||||
from module import (foo, bar,)
|
||||
from module import (foo, bar, baz, quux)
|
||||
from module import (foo,
|
||||
bar)
|
||||
from module import (foo,
|
||||
bar,
|
||||
)
|
||||
from module import (
|
||||
foo,
|
||||
bar)
|
||||
@@ -0,0 +1,17 @@
|
||||
from module import (foo, bar)
|
||||
from module import (foo, bar, )
|
||||
from module import (
|
||||
foo, bar, baz, quux
|
||||
)
|
||||
from module import (
|
||||
foo,
|
||||
bar
|
||||
)
|
||||
from module import (
|
||||
foo,
|
||||
bar,
|
||||
)
|
||||
from module import (
|
||||
foo,
|
||||
bar
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
from module import (foo, bar)
|
||||
from module import (foo, bar, )
|
||||
from module import (
|
||||
foo, bar, baz, quux
|
||||
)
|
||||
from module import (
|
||||
foo,
|
||||
bar
|
||||
)
|
||||
from module import (
|
||||
foo,
|
||||
bar,
|
||||
)
|
||||
from module import (
|
||||
foo,
|
||||
bar
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
from module import foo
|
||||
from module import foo, bar
|
||||
from module import foo, bar,
|
||||
# | margin
|
||||
from module import foo, bar, baz
|
||||
from module import foo, \
|
||||
bar
|
||||
from module import foo, \
|
||||
bar,
|
||||
|
||||
from module import foo, \
|
||||
bar # comment
|
||||
|
||||
from module import (foo,
|
||||
bar)
|
||||
|
||||
from module import (foo,
|
||||
bar,)
|
||||
|
||||
from module import (
|
||||
foo,
|
||||
bar # comment
|
||||
)
|
||||
@@ -0,0 +1,25 @@
|
||||
from module import foo
|
||||
from module import foo, bar
|
||||
from module import foo, bar,
|
||||
# | margin
|
||||
from module import (foo, bar,
|
||||
baz, )
|
||||
from module import (foo,
|
||||
bar, )
|
||||
from module import (foo,
|
||||
bar, )
|
||||
|
||||
from module import (foo,
|
||||
bar, # comment
|
||||
)
|
||||
|
||||
from module import (foo,
|
||||
bar, )
|
||||
|
||||
from module import (foo,
|
||||
bar, )
|
||||
|
||||
from module import (
|
||||
foo,
|
||||
bar, # comment
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
from module import foo
|
||||
from module import foo, bar
|
||||
from module import foo, bar,
|
||||
# | margin
|
||||
from module import foo, bar, baz
|
||||
from module import foo, \
|
||||
bar
|
||||
from module import foo, \
|
||||
bar,
|
||||
|
||||
from module import foo, \
|
||||
bar # comment
|
||||
|
||||
from module import (foo,
|
||||
bar)
|
||||
|
||||
from module import (foo,
|
||||
bar,)
|
||||
|
||||
from module import (
|
||||
foo,
|
||||
bar # comment
|
||||
)
|
||||
@@ -0,0 +1,24 @@
|
||||
from module import foo
|
||||
from module import foo, bar
|
||||
from module import foo, bar,
|
||||
# | margin
|
||||
from module import foo, bar, \
|
||||
baz
|
||||
from module import foo, \
|
||||
bar
|
||||
from module import foo, \
|
||||
bar,
|
||||
|
||||
from module import foo, \
|
||||
bar # comment
|
||||
|
||||
from module import (foo,
|
||||
bar, )
|
||||
|
||||
from module import (foo,
|
||||
bar, )
|
||||
|
||||
from module import (
|
||||
foo,
|
||||
bar, # comment
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
from module import foo, bar
|
||||
from module import foo, bar, baz
|
||||
from module import (foo, bar)
|
||||
from module import (foo, bar, baz)
|
||||
@@ -0,0 +1,8 @@
|
||||
from module import foo, bar
|
||||
from module import foo, \
|
||||
bar, \
|
||||
baz
|
||||
from module import (foo, bar)
|
||||
from module import (foo,
|
||||
bar,
|
||||
baz)
|
||||
@@ -0,0 +1,3 @@
|
||||
d = {"foo": 1,
|
||||
"bar": 2
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
d = {
|
||||
"foo": 1,
|
||||
"bar": 2
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
from module import foo
|
||||
from module import foo, bar
|
||||
from module import foo, \
|
||||
bar
|
||||
from module import foo, \
|
||||
bar # comment
|
||||
from module import foo, \
|
||||
\
|
||||
\
|
||||
bar,
|
||||
@@ -0,0 +1,18 @@
|
||||
from module import foo
|
||||
from module import (
|
||||
foo,
|
||||
bar,
|
||||
)
|
||||
from module import (
|
||||
foo,
|
||||
bar,
|
||||
)
|
||||
from module import (
|
||||
foo,
|
||||
bar, # comment
|
||||
)
|
||||
from module import (
|
||||
foo,
|
||||
|
||||
bar,
|
||||
)
|
||||
@@ -484,6 +484,11 @@ public class PyFormatterTest extends PyTestCase {
|
||||
public void testAlignmentOfClosingBraceInDictLiteralWhenNoHangingIndent() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNoAlignmentClosingBraceInDictLiteralWhenOpeningBraceIsForcedOnNewLine() {
|
||||
getPythonCodeStyleSettings().DICT_NEW_LINE_AFTER_LEFT_BRACE = true;
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-13004
|
||||
public void testAlignmentOfClosingParenthesisOfArgumentListWhenNoHangingIndent() {
|
||||
@@ -733,6 +738,67 @@ public class PyFormatterTest extends PyTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-20633
|
||||
public void testFromImportWrappingChopDownIfLong() {
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_WRAPPING = WrapType.CHOP_DOWN_IF_LONG.getLegacyRepresentation();
|
||||
getCodeStyleSettings().setRightMargin(PythonLanguage.INSTANCE, 30);
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-20633
|
||||
public void testFromImportParenthesesPlacement() {
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_NEW_LINE_AFTER_LEFT_PARENTHESIS = true;
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_NEW_LINE_BEFORE_RIGHT_PARENTHESIS = true;
|
||||
getCommonCodeStyleSettings().SPACE_AFTER_COLON = true;
|
||||
getCodeStyleSettings().setRightMargin(PythonLanguage.INSTANCE, 35);
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-20633
|
||||
public void testFromImportParenthesesPlacementHangClosingParenthesis() {
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_NEW_LINE_AFTER_LEFT_PARENTHESIS = true;
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_NEW_LINE_BEFORE_RIGHT_PARENTHESIS = true;
|
||||
getPythonCodeStyleSettings().HANG_CLOSING_BRACKETS = true;
|
||||
getCommonCodeStyleSettings().SPACE_AFTER_COLON = true;
|
||||
getCodeStyleSettings().setRightMargin(PythonLanguage.INSTANCE, 35);
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-20633
|
||||
public void testFromImportForceParenthesesIfMultiline() {
|
||||
getCodeStyleSettings().setRightMargin(PythonLanguage.INSTANCE, 30);
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_PARENTHESES_FORCE_IF_MULTILINE = true;
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-20633
|
||||
// See http://docs.pylonsproject.org/en/latest/community/codestyle.html
|
||||
public void testPyramidFromImportFormatting() {
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_PARENTHESES_FORCE_IF_MULTILINE = true;
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_NEW_LINE_AFTER_LEFT_PARENTHESIS = true;
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_NEW_LINE_BEFORE_RIGHT_PARENTHESIS = true;
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_WRAPPING = WrapType.ALWAYS.getLegacyRepresentation();
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_TRAILING_COMMA_IF_MULTILINE = true;
|
||||
getPythonCodeStyleSettings().HANG_CLOSING_BRACKETS = true;
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-9764
|
||||
public void testFromImportTrailingCommaWithParentheses() {
|
||||
getCodeStyleSettings().setRightMargin(PythonLanguage.INSTANCE, 30);
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_PARENTHESES_FORCE_IF_MULTILINE = true;
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_TRAILING_COMMA_IF_MULTILINE = true;
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-9764
|
||||
public void testFromImportTrailingCommaWithoutParentheses() {
|
||||
getCodeStyleSettings().setRightMargin(PythonLanguage.INSTANCE, 30);
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_PARENTHESES_FORCE_IF_MULTILINE = false;
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_TRAILING_COMMA_IF_MULTILINE = true;
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testVariableAnnotations() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON36, this::doTest);
|
||||
}
|
||||
|
||||