Skip to content

Commit

Permalink
TOML: add initial formatter implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Kobzol committed Jul 20, 2020
1 parent 8f55b2d commit e38f2dc
Show file tree
Hide file tree
Showing 20 changed files with 429 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.toml.ide.formatter

import com.intellij.formatting.*
import com.intellij.lang.ASTNode
import com.intellij.openapi.util.TextRange
import com.intellij.psi.formatter.FormatterUtil
import org.toml.ide.formatter.impl.computeIndent
import org.toml.ide.formatter.impl.computeSpacing
import org.toml.ide.formatter.impl.isWhitespaceOrEmpty
import org.toml.lang.psi.TomlElementTypes.ARRAY

class TomlFmtBlock(
private val node: ASTNode,
private val alignment: Alignment?,
private val indent: Indent?,
private val wrap: Wrap?,
private val ctx: TomlFmtContext
) : ASTBlock {
override fun getNode(): ASTNode = node
override fun getTextRange(): TextRange = node.textRange
override fun getAlignment(): Alignment? = alignment
override fun getIndent(): Indent? = indent
override fun getWrap(): Wrap? = wrap

override fun getSubBlocks(): List<Block> = mySubBlocks
private val mySubBlocks: List<Block> by lazy { buildChildren() }

private fun buildChildren(): List<Block> {
return node.getChildren(null)
.filter { !it.isWhitespaceOrEmpty() }
.map { childNode: ASTNode ->
TomlFormattingModelBuilder.createBlock(
node = childNode,
alignment = null,
indent = computeIndent(childNode),
wrap = null,
ctx = ctx
)
}
}

override fun getSpacing(child1: Block?, child2: Block): Spacing? = computeSpacing(child1, child2, ctx)

override fun getChildAttributes(newChildIndex: Int): ChildAttributes {
val indent = when (node.elementType) {
ARRAY -> Indent.getNormalIndent()
else -> Indent.getNoneIndent()
}
return ChildAttributes(indent, null)
}

override fun isLeaf(): Boolean = node.firstChildNode == null

override fun isIncomplete(): Boolean = myIsIncomplete
private val myIsIncomplete: Boolean by lazy { FormatterUtil.isIncomplete(node) }

override fun toString() = "${node.text} $textRange"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.toml.ide.formatter

import com.intellij.formatting.SpacingBuilder
import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.codeStyle.CommonCodeStyleSettings
import org.toml.ide.formatter.impl.createSpacingBuilder
import org.toml.lang.TomlLanguage

data class TomlFmtContext private constructor(
val commonSettings: CommonCodeStyleSettings,
val spacingBuilder: SpacingBuilder
) {
companion object {
fun create(settings: CodeStyleSettings): TomlFmtContext {
val commonSettings = settings.getCommonSettings(TomlLanguage)
return TomlFmtContext(commonSettings, createSpacingBuilder(commonSettings))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.toml.ide.formatter

import com.intellij.formatting.*
import com.intellij.lang.ASTNode
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.codeStyle.CodeStyleSettings

class TomlFormattingModelBuilder : FormattingModelBuilder {
override fun getRangeAffectingIndent(file: PsiFile?, offset: Int, elementAtOffset: ASTNode?): TextRange? = null

override fun createModel(element: PsiElement, settings: CodeStyleSettings): FormattingModel {
val ctx = TomlFmtContext.create(settings)
val block = createBlock(element.node, null, Indent.getNoneIndent(), null, ctx)
return FormattingModelProvider.createFormattingModelForPsiFile(element.containingFile, block, settings)
}

companion object {
fun createBlock(
node: ASTNode,
alignment: Alignment?,
indent: Indent?,
wrap: Wrap?,
ctx: TomlFmtContext
): ASTBlock = TomlFmtBlock(node, alignment, indent, wrap, ctx)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.toml.ide.formatter.impl

import com.intellij.formatting.Indent
import com.intellij.lang.ASTNode
import org.toml.ide.formatter.TomlFmtBlock
import org.toml.lang.psi.TomlElementTypes.ARRAY

fun TomlFmtBlock.computeIndent(child: ASTNode): Indent? = when (node.elementType) {
ARRAY -> getArrayIndent(child)
else -> Indent.getNoneIndent()
}

private fun getArrayIndent(node: ASTNode): Indent =
when {
node.isArrayDelimiter() -> Indent.getNoneIndent()
else -> Indent.getNormalIndent()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.toml.ide.formatter.impl

import com.intellij.formatting.Block
import com.intellij.formatting.Spacing
import com.intellij.formatting.SpacingBuilder
import com.intellij.psi.codeStyle.CommonCodeStyleSettings
import org.toml.ide.formatter.TomlFmtContext
import org.toml.lang.psi.TomlElementTypes.*
import com.intellij.psi.tree.TokenSet.create as ts

fun createSpacingBuilder(commonSettings: CommonCodeStyleSettings): SpacingBuilder {
val builder = SpacingBuilder(commonSettings)
// ,
.after(COMMA).spacing(1, 1, 0, true, 0)
.before(COMMA).spaceIf(false)
// =
.around(EQ).spacing(1, 1, 0, true, 0)
// [ ]
.after(L_BRACKET).spaceIf(false)
.before(R_BRACKET).spaceIf(false)
// { }
.after(L_CURLY).spaceIf(true)
.before(R_CURLY).spaceIf(true)
// .
.aroundInside(DOT, ts(KEY, TABLE_HEADER)).spaceIf(false)

return builder
}

fun Block.computeSpacing(child1: Block?, child2: Block, ctx: TomlFmtContext): Spacing? {
return ctx.spacingBuilder.getSpacing(this, child1, child2)
}
17 changes: 17 additions & 0 deletions intellij-toml/src/main/kotlin/org/toml/ide/formatter/impl/utils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.toml.ide.formatter.impl

import com.intellij.lang.ASTNode
import com.intellij.psi.TokenType
import com.intellij.psi.tree.TokenSet
import org.toml.lang.psi.TomlElementTypes.*

val ARRAY_DELIMITERS = TokenSet.create(L_BRACKET, R_BRACKET)

fun ASTNode?.isWhitespaceOrEmpty() = this == null || textLength == 0 || elementType == TokenType.WHITE_SPACE

fun ASTNode.isArrayDelimiter(): Boolean = elementType in ARRAY_DELIMITERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.toml.ide.formatter.settings

import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.codeStyle.CustomCodeStyleSettings

class TomlCodeStyleSettings(container: CodeStyleSettings) :
CustomCodeStyleSettings(TomlCodeStyleSettings::class.java.simpleName, container)
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.toml.ide.formatter.settings

import com.intellij.application.options.CodeStyleAbstractConfigurable
import com.intellij.application.options.TabbedLanguageCodeStylePanel
import com.intellij.openapi.options.Configurable
import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.codeStyle.CodeStyleSettingsProvider
import com.intellij.psi.codeStyle.CustomCodeStyleSettings
import org.toml.lang.TomlLanguage

class TomlCodeStyleSettingsProvider : CodeStyleSettingsProvider() {
override fun createCustomSettings(settings: CodeStyleSettings): CustomCodeStyleSettings = TomlCodeStyleSettings(settings)

override fun getConfigurableDisplayName(): String = TomlLanguage.displayName

override fun createSettingsPage(settings: CodeStyleSettings, originalSettings: CodeStyleSettings): Configurable =
object : CodeStyleAbstractConfigurable(settings, originalSettings, configurableDisplayName) {
override fun createPanel(settings: CodeStyleSettings) = TomlCodeStyleMainPanel(currentSettings, settings)
override fun getHelpTopic() = null
}

private class TomlCodeStyleMainPanel(currentSettings: CodeStyleSettings, settings: CodeStyleSettings) :
TabbedLanguageCodeStylePanel(TomlLanguage, currentSettings, settings) {

override fun initTabs(settings: CodeStyleSettings) {
addIndentOptionsTab(settings)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.toml.ide.formatter.settings

import com.intellij.application.options.IndentOptionsEditor
import com.intellij.application.options.SmartIndentOptionsEditor
import com.intellij.lang.Language
import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider
import org.toml.lang.TomlLanguage

class TomlLanguageCodeStyleSettingsProvider : LanguageCodeStyleSettingsProvider() {
override fun getLanguage(): Language = TomlLanguage
override fun getCodeSample(settingsType: SettingsType): String =
when (settingsType) {
SettingsType.INDENT_SETTINGS -> INDENT_SAMPLE
else -> ""
}

override fun getIndentOptionsEditor(): IndentOptionsEditor? = SmartIndentOptionsEditor()
}

private fun sample(@org.intellij.lang.annotations.Language("TOML") code: String) = code.trim()

private val INDENT_SAMPLE = sample("""
[config]
items = [
"foo",
"bar"
]
""")
12 changes: 12 additions & 0 deletions intellij-toml/src/main/kotlin/org/toml/ide/formatter/utils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.toml.ide.formatter

import com.intellij.psi.codeStyle.CodeStyleSettings
import org.toml.ide.formatter.settings.TomlCodeStyleSettings

val CodeStyleSettings.toml: TomlCodeStyleSettings
get() = getCustomSettings(TomlCodeStyleSettings::class.java)
7 changes: 7 additions & 0 deletions intellij-toml/src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@
<lang.quoteHandler language="TOML" implementationClass="org.toml.ide.TomlQuoteHandler"/>
<lang.elementManipulator forClass="org.toml.lang.psi.TomlLiteral"
implementationClass="org.toml.lang.psi.TomlStringLiteralManipulator"/>

<!-- Formatting -->
<lang.formatter language="TOML" implementationClass="org.toml.ide.formatter.TomlFormattingModelBuilder"/>
<codeStyleSettingsProvider implementation="org.toml.ide.formatter.settings.TomlCodeStyleSettingsProvider"/>
<langCodeStyleSettingsProvider
implementation="org.toml.ide.formatter.settings.TomlLanguageCodeStyleSettingsProvider"/>

<colorSettingsPage implementation="org.toml.ide.colors.TomlColorSettingsPage"/>
<indexPatternBuilder implementation="org.toml.ide.todo.TomlTodoIndexPatternBuilder"/>
<todoIndexer filetype="TOML" implementationClass="org.toml.ide.todo.TomlTodoIndexer"/>
Expand Down
26 changes: 26 additions & 0 deletions intellij-toml/src/test/kotlin/org/toml/TomlTestBase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.toml

import com.intellij.testFramework.fixtures.BasePlatformTestCase
import org.intellij.lang.annotations.Language

abstract class TomlTestBase : BasePlatformTestCase() {
@Suppress("TestFunctionName")
protected fun InlineFile(@Language("TOML") text: String, name: String = "example.toml") {
myFixture.configureByText(name, text)
}

protected fun checkByText(
@Language("TOML") before: String,
@Language("TOML") after: String,
action: () -> Unit
) {
InlineFile(before)
action()
myFixture.checkResult(after)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
package org.toml.ide.annotator

import com.intellij.openapiext.Testmark
import com.intellij.testFramework.fixtures.BasePlatformTestCase
import org.intellij.lang.annotations.Language
import org.toml.TomlTestBase

abstract class TomlAnnotationTestBase() : BasePlatformTestCase() {
abstract class TomlAnnotationTestBase : TomlTestBase() {

protected lateinit var annotationFixture: TomlAnnotationTestFixture

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.toml.ide.formatter

import org.toml.ide.typing.TomlTypingTestBase

class TomlAutoIndentTest : TomlTypingTestBase() {
fun `test new array element`() = doTestByText("""
[key]
foo = [
"text",<caret>
]
""", """
[key]
foo = [
"text",
<caret>
]
""")
}
Loading

0 comments on commit e38f2dc

Please sign in to comment.