diff --git a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt index 63bc5acb71..9541cdcbba 100644 --- a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt +++ b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt @@ -97,9 +97,13 @@ object Main { private var android: Boolean = false // todo: make it a command in 1.0.0 (it's too late now as we might interfere with valid "lint" patterns) - @Option(names = arrayOf("--apply-to-idea"), description = arrayOf("Update Intellij IDEA project settings")) + @Option(names = arrayOf("--apply-to-idea"), description = arrayOf("Update Intellij IDEA settings (global)")) private var apply: Boolean = false + // todo: make it a command in 1.0.0 (it's too late now as we might interfere with valid "lint" patterns) + @Option(names = arrayOf("--apply-to-idea-project"), description = arrayOf("Update Intellij IDEA project settings")) + private var applyToProject: Boolean = false + @Option(names = arrayOf("--color"), description = arrayOf("Make output colorful")) private var color: Boolean = false @@ -234,7 +238,7 @@ object Main { exitProcess(0) } } - if (apply) { + if (apply || applyToProject) { applyToIDEA() exitProcess(0) } @@ -487,13 +491,12 @@ object Main { try { val workDir = Paths.get(".") if (!forceApply) { - val fileList = IntellijIDEAIntegration.apply(workDir, true, android) + val fileList = IntellijIDEAIntegration.apply(workDir, true, android, applyToProject) System.err.println("The following files are going to be updated:\n\n\t" + fileList.joinToString("\n\t") + "\n\nDo you wish to proceed? [y/n]\n" + "(in future, use -y flag if you wish to skip confirmation)") val scanner = Scanner(System.`in`) - val res = generateSequence { try { scanner.next() } catch (e: NoSuchElementException) { null } } @@ -504,7 +507,7 @@ object Main { exitProcess(1) } } - IntellijIDEAIntegration.apply(workDir, false, android) + IntellijIDEAIntegration.apply(workDir, false, android, applyToProject) } catch (e: IntellijIDEAIntegration.ProjectNotFoundException) { System.err.println(".idea directory not found. " + "Are you sure you are inside project root directory?") diff --git a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/IntellijIDEAIntegration.kt b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/IntellijIDEAIntegration.kt index 0dc0e92364..0e3cc9f1e8 100644 --- a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/IntellijIDEAIntegration.kt +++ b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/IntellijIDEAIntegration.kt @@ -22,71 +22,101 @@ object IntellijIDEAIntegration { @Suppress("UNUSED_PARAMETER") @Throws(IOException::class) - fun apply(workDir: Path, dryRun: Boolean, android: Boolean = false): Array { + fun apply(workDir: Path, dryRun: Boolean, android: Boolean = false, local: Boolean = false): Array { if (!Files.isDirectory(workDir.resolve(".idea"))) { throw ProjectNotFoundException() } - val home = System.getProperty("user.home") val editorConfig: Map = EditorConfig.of(".") ?: emptyMap() - val continuationIndentSize = editorConfig["continuation_indent_size"]?.toIntOrNull() ?: 4 val indentSize = editorConfig["indent_size"]?.toIntOrNull() ?: 4 - val codeStyleName = "ktlint${ - if (continuationIndentSize == 4) "" else "-cis$continuationIndentSize" - }${ - if (indentSize == 4) "" else "-is$indentSize" - }" - val paths = - // macOS - Glob.from("IntelliJIdea*", "IdeaIC*", "AndroidStudio*") - .iterate(Paths.get(home, "Library", "Preferences"), - Glob.IterationOption.SKIP_CHILDREN, Glob.IterationOption.DIRECTORY).asSequence() + - // linux/windows - Glob.from(".IntelliJIdea*/config", ".IdeaIC*/config", ".AndroidStudio*/config") - .iterate(Paths.get(home), - Glob.IterationOption.SKIP_CHILDREN, Glob.IterationOption.DIRECTORY).asSequence() - val updates = (paths.flatMap { dir -> - sequenceOf( - Paths.get(dir.toString(), "codestyles", "$codeStyleName.xml") to - overwriteWithResource("/config/codestyles/ktlint.xml") { resource -> + val continuationIndentSize = editorConfig["continuation_indent_size"]?.toIntOrNull() ?: 4 + val updates = if (local) { + listOf( + Paths.get(workDir.toString(), ".idea", "codeStyles", "codeStyleConfig.xml") to + overwriteWithResource("/project-config/.idea/codeStyles/codeStyleConfig.xml"), + Paths.get(workDir.toString(), ".idea", "codeStyles", "Project.xml") to + overwriteWithResource("/project-config/.idea/codeStyles/Project.xml") { resource -> resource - .replace("code_scheme name=\"ktlint\"", - "code_scheme name=\"$codeStyleName\"") .replace("option name=\"INDENT_SIZE\" value=\"4\"", "option name=\"INDENT_SIZE\" value=\"$indentSize\"") .replace("option name=\"CONTINUATION_INDENT_SIZE\" value=\"8\"", "option name=\"CONTINUATION_INDENT_SIZE\" value=\"$continuationIndentSize\"") }, - Paths.get(dir.toString(), "options", "code.style.schemes.xml") to - overwriteWithResource("/config/options/code.style.schemes.xml") { content -> - content - .replace("option name=\"CURRENT_SCHEME_NAME\" value=\"ktlint\"", - "option name=\"CURRENT_SCHEME_NAME\" value=\"$codeStyleName\"") - }, - Paths.get(dir.toString(), "inspection", "ktlint.xml") to - overwriteWithResource("/config/inspection/ktlint.xml"), - Paths.get(dir.toString(), "options", "editor.codeinsight.xml") to { - var arr = "".toByteArray() + Paths.get(workDir.toString(), ".idea", "inspectionProfiles", "profiles_settings.xml") to + overwriteWithResource("/project-config/.idea/inspectionProfiles/profiles_settings.xml"), + Paths.get(workDir.toString(), ".idea", "inspectionProfiles", "ktlint.xml") to + overwriteWithResource("/project-config/.idea/inspectionProfiles/ktlint.xml"), + Paths.get(workDir.toString(), ".idea", "workspace.xml") to { + var arr = "".toByteArray() try { - arr = Files.readAllBytes(Paths.get(dir.toString(), "options", "editor.codeinsight.xml")) + arr = Files.readAllBytes(Paths.get(workDir.toString(), ".idea", "workspace.xml")) } catch (e: IOException) { if (e !is NoSuchFileException) { throw e } } - enableOptimizeImportsOnTheFly(arr) + enableOptimizeImportsOnTheFlyInsideWorkspace(arr) } ) - } + sequenceOf( - Paths.get(workDir.toString(), ".idea", "codeStyleSettings.xml") to - overwriteWithResource("/config/.idea/codeStyleSettings.xml") { content -> - content.replace( - "option name=\"PREFERRED_PROJECT_CODE_STYLE\" value=\"ktlint\"", - "option name=\"PREFERRED_PROJECT_CODE_STYLE\" value=\"$codeStyleName\"" - ) - }, - Paths.get(workDir.toString(), ".idea", "inspectionProfiles", "profiles_settings.xml") to - overwriteWithResource("/config/.idea/inspectionProfiles/profiles_settings.xml") - )).toList() + } else { + val home = System.getProperty("user.home") + val codeStyleName = "ktlint${ + if (continuationIndentSize == 4) "" else "-cis$continuationIndentSize" + }${ + if (indentSize == 4) "" else "-is$indentSize" + }" + val paths = + // macOS + Glob.from("IntelliJIdea*", "IdeaIC*", "AndroidStudio*") + .iterate(Paths.get(home, "Library", "Preferences"), + Glob.IterationOption.SKIP_CHILDREN, Glob.IterationOption.DIRECTORY).asSequence() + + // linux/windows + Glob.from(".IntelliJIdea*/config", ".IdeaIC*/config", ".AndroidStudio*/config") + .iterate(Paths.get(home), + Glob.IterationOption.SKIP_CHILDREN, Glob.IterationOption.DIRECTORY).asSequence() + (paths.flatMap { dir -> + sequenceOf( + Paths.get(dir.toString(), "codestyles", "$codeStyleName.xml") to + overwriteWithResource("/config/codestyles/ktlint.xml") { resource -> + resource + .replace("code_scheme name=\"ktlint\"", + "code_scheme name=\"$codeStyleName\"") + .replace("option name=\"INDENT_SIZE\" value=\"4\"", + "option name=\"INDENT_SIZE\" value=\"$indentSize\"") + .replace("option name=\"CONTINUATION_INDENT_SIZE\" value=\"8\"", + "option name=\"CONTINUATION_INDENT_SIZE\" value=\"$continuationIndentSize\"") + }, + Paths.get(dir.toString(), "options", "code.style.schemes.xml") to + overwriteWithResource("/config/options/code.style.schemes.xml") { content -> + content + .replace("option name=\"CURRENT_SCHEME_NAME\" value=\"ktlint\"", + "option name=\"CURRENT_SCHEME_NAME\" value=\"$codeStyleName\"") + }, + Paths.get(dir.toString(), "inspection", "ktlint.xml") to + overwriteWithResource("/config/inspection/ktlint.xml"), + Paths.get(dir.toString(), "options", "editor.codeinsight.xml") to { + var arr = "".toByteArray() + try { + arr = Files.readAllBytes(Paths.get(dir.toString(), "options", "editor.codeinsight.xml")) + } catch (e: IOException) { + if (e !is NoSuchFileException) { + throw e + } + } + enableOptimizeImportsOnTheFly(arr) + } + ) + } + sequenceOf( + Paths.get(workDir.toString(), ".idea", "codeStyleSettings.xml") to + overwriteWithResource("/config/.idea/codeStyleSettings.xml") { content -> + content.replace( + "option name=\"PREFERRED_PROJECT_CODE_STYLE\" value=\"ktlint\"", + "option name=\"PREFERRED_PROJECT_CODE_STYLE\" value=\"$codeStyleName\"" + ) + }, + Paths.get(workDir.toString(), ".idea", "inspectionProfiles", "profiles_settings.xml") to + overwriteWithResource("/config/.idea/inspectionProfiles/profiles_settings.xml") + )).toList() + } if (!dryRun) { updates.forEach { (path, contentSupplier) -> Files.createDirectories(path.parent) @@ -134,6 +164,39 @@ object IntellijIDEAIntegration { return out.toByteArray() } + private fun enableOptimizeImportsOnTheFlyInsideWorkspace(arr: ByteArray): ByteArray { + /* + + + + ... + + */ + val doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(ByteArrayInputStream(arr)) + val xpath = XPathFactory.newInstance().newXPath() + var cis = xpath.evaluate("//component[@name='CodeInsightWorkspaceSettings']", + doc, XPathConstants.NODE) as Element? + if (cis == null) { + cis = doc.createElement("component") + cis.setAttribute("name", "CodeInsightWorkspaceSettings") + cis = doc.documentElement.appendChild(cis) as Element + } + var oiotf = xpath.evaluate("//option[@name='optimizeImportsOnTheFly']", + cis, XPathConstants.NODE) as Element? + if (oiotf == null) { + oiotf = doc.createElement("option") + oiotf.setAttribute("name", "optimizeImportsOnTheFly") + oiotf = cis.appendChild(oiotf) as Element + } + oiotf.setAttribute("value", "true") + val transformer = TransformerFactory.newInstance().newTransformer() + val out = ByteArrayOutputStream() + transformer.transform(DOMSource(doc), StreamResult(out)) + return out.toByteArray() + } + private fun getResourceText(name: String) = this::class.java.getResourceAsStream(name).readBytes().toString(Charset.forName("UTF-8")) diff --git a/ktlint/src/main/resources/config/codestyles/ktlint.xml b/ktlint/src/main/resources/config/codestyles/ktlint.xml index b18d73a4d9..e73bb3bae6 100644 --- a/ktlint/src/main/resources/config/codestyles/ktlint.xml +++ b/ktlint/src/main/resources/config/codestyles/ktlint.xml @@ -7,9 +7,10 @@