Skip to content

Commit

Permalink
Added --apply-to-idea-project (#178)
Browse files Browse the repository at this point in the history
  • Loading branch information
shyiko committed Apr 22, 2018
1 parent 55464ea commit 8542181
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 53 deletions.
13 changes: 8 additions & 5 deletions ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -234,7 +238,7 @@ object Main {
exitProcess(0)
}
}
if (apply) {
if (apply || applyToProject) {
applyToIDEA()
exitProcess(0)
}
Expand Down Expand Up @@ -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 }
}
Expand All @@ -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?")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,71 +22,101 @@ object IntellijIDEAIntegration {

@Suppress("UNUSED_PARAMETER")
@Throws(IOException::class)
fun apply(workDir: Path, dryRun: Boolean, android: Boolean = false): Array<Path> {
fun apply(workDir: Path, dryRun: Boolean, android: Boolean = false, local: Boolean = false): Array<Path> {
if (!Files.isDirectory(workDir.resolve(".idea"))) {
throw ProjectNotFoundException()
}
val home = System.getProperty("user.home")
val editorConfig: Map<String, String> = 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 = "<application></application>".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 = "<project version=\"4\"></project>".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 = "<application></application>".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)
Expand Down Expand Up @@ -134,6 +164,39 @@ object IntellijIDEAIntegration {
return out.toByteArray()
}

private fun enableOptimizeImportsOnTheFlyInsideWorkspace(arr: ByteArray): ByteArray {
/*
<project>
<component name="CodeInsightWorkspaceSettings">
<option name="optimizeImportsOnTheFly" value="false" />
...
</component>
...
</project>
*/
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"))

Expand Down
3 changes: 2 additions & 1 deletion ktlint/src/main/resources/config/codestyles/ktlint.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
</option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="CONTINUATION_INDENT_IN_PARAMETER_LISTS" value="false" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
Expand Down
1 change: 0 additions & 1 deletion ktlint/src/main/resources/config/inspection/ktlint.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@
<option name="myName" value="ktlint" />
<inspection_tool class="KotlinUnusedImport" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="RedundantSemicolon" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="UnusedSymbol" enabled="true" level="ERROR" enabled_by_default="true" />
</inspections>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 8542181

Please sign in to comment.