Merge remote-tracking branch 'origin/master'

This commit is contained in:
Roman Shevchenko
2017-01-23 17:49:37 +03:00
56 changed files with 1659 additions and 48 deletions
+1
View File
@@ -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();
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

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,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 B

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

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
}
@@ -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);
}