Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more settings to IJ Plugin UI #503

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,27 @@

package com.facebook.ktfmt.intellij

import com.facebook.ktfmt.format.FormattingOptions
import com.facebook.ktfmt.intellij.KtfmtSettings.EnabledState.Disabled
import com.facebook.ktfmt.intellij.KtfmtSettings.EnabledState.Enabled
import com.facebook.ktfmt.intellij.UiFormatterStyle.Custom
import com.facebook.ktfmt.intellij.UiFormatterStyle.Google
import com.facebook.ktfmt.intellij.UiFormatterStyle.KotlinLang
import com.facebook.ktfmt.intellij.UiFormatterStyle.Meta
import com.intellij.openapi.options.BoundSearchableConfigurable
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.ui.DialogPanel
import com.intellij.ui.dsl.builder.BottomGap
import com.intellij.ui.dsl.builder.Cell
import com.intellij.ui.dsl.builder.bindIntText
import com.intellij.ui.dsl.builder.bindItem
import com.intellij.ui.dsl.builder.bindSelected
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.layout.selected
import com.intellij.ui.layout.selectedValueMatches
import javax.swing.JCheckBox
import javax.swing.JTextField

@Suppress("DialogTitleCapitalization")
class KtfmtConfigurable(project: Project) :
Expand All @@ -48,14 +59,133 @@ class KtfmtConfigurable(project: Project) :
.component
}

lateinit var styleComboBox: ComboBox<UiFormatterStyle>
row {
comboBox(UiFormatterStyle.entries.toList())
.label("Code style:")
.bindItem(
getter = { settings.uiFormatterStyle },
setter = { settings.uiFormatterStyle = it ?: UiFormatterStyle.Meta },
)
.enabledIf(enabledCheckbox.selected)
styleComboBox =
comboBox(listOf(Meta, Google, KotlinLang, Custom))
.label("Code style:")
.bindItem(
getter = { settings.uiFormatterStyle },
setter = { settings.uiFormatterStyle = it ?: Meta },
)
.enabledIf(enabledCheckbox.selected)
.component
}

group("Custom style") {
lateinit var maxLineLength: JTextField
row("Max line length:") {
maxLineLength =
textField()
.bindIntText(settings::customMaxLineLength)
.validatePositiveIntegerOrEmpty()
.component
}

lateinit var blockIndent: JTextField
row("Block indent size:") {
blockIndent =
textField()
.bindIntText(settings::customBlockIndent)
.validatePositiveIntegerOrEmpty()
.component
}

lateinit var continuationIndent: JTextField
row("Continuation indent size:") {
continuationIndent =
textField()
.bindIntText(settings::customContinuationIndent)
.validatePositiveIntegerOrEmpty()
.component
}

lateinit var manageTrailingCommas: JCheckBox
row {
manageTrailingCommas =
checkBox("Manage trailing commas")
.bindSelected(settings::customManageTrailingCommas)
.component
}

lateinit var removeUnusedImports: JCheckBox
row {
removeUnusedImports =
checkBox("Remove unused imports")
.bindSelected(settings::customRemoveUnusedImports)
.component
}
.bottomGap(BottomGap.SMALL)

row("Copy from:") {
// Note: updating must be done via the components, and not the settings,
// or the Kotlin DSL bindings will overwrite the values when applying
link(Meta.toString()) {
UiFormatterStyle.getStandardFormattingOptions(Meta)
.updateFields(
maxLineLength,
blockIndent,
continuationIndent,
manageTrailingCommas,
removeUnusedImports,
)
}
.component
.autoHideOnDisable = false

link(Google.toString()) {
UiFormatterStyle.getStandardFormattingOptions(Google)
.updateFields(
maxLineLength,
blockIndent,
continuationIndent,
manageTrailingCommas,
removeUnusedImports,
)
}
.component
.autoHideOnDisable = false

link(KotlinLang.toString()) {
UiFormatterStyle.getStandardFormattingOptions(KotlinLang)
.updateFields(
maxLineLength,
blockIndent,
continuationIndent,
manageTrailingCommas,
removeUnusedImports,
)
}
.component
.autoHideOnDisable = false
}
}
.visibleIf(styleComboBox.selectedValueMatches { it == Custom })
.enabledIf(enabledCheckbox.selected)
}
}

private fun FormattingOptions.updateFields(
maxLineLength: JTextField,
blockIndent: JTextField,
continuationIndent: JTextField,
manageTrailingCommas: JCheckBox,
removeUnusedImports: JCheckBox,
) {
maxLineLength.text = maxWidth.toString()
blockIndent.text = this.blockIndent.toString()
continuationIndent.text = this.continuationIndent.toString()
manageTrailingCommas.isSelected = this.manageTrailingCommas
removeUnusedImports.isSelected = this.removeUnusedImports
}

private fun Cell<JTextField>.validatePositiveIntegerOrEmpty() = validationOnInput { jTextField ->
if (jTextField.text.isNotEmpty()) {
val parsedValue = jTextField.text.toIntOrNull()
when {
parsedValue == null -> error("Value must be an integer. Will default to 1")
parsedValue <= 0 -> error("Value must be greater than zero. Will default to 1")
else -> null
}
} else null
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.facebook.ktfmt.intellij

import com.facebook.ktfmt.format.Formatter.format
import com.facebook.ktfmt.format.FormattingOptions
import com.google.googlejavaformat.java.FormatterException
import com.intellij.formatting.service.AsyncDocumentFormattingService
import com.intellij.formatting.service.AsyncFormattingRequest
Expand All @@ -31,8 +32,15 @@ private const val PARSING_ERROR_TITLE: String = PARSING_ERROR_NOTIFICATION_GROUP
class KtfmtFormattingService : AsyncDocumentFormattingService() {
override fun createFormattingTask(request: AsyncFormattingRequest): FormattingTask {
val project = request.context.project
val style = KtfmtSettings.getInstance(project).uiFormatterStyle
return KtfmtFormattingTask(request, style)
val settings = KtfmtSettings.getInstance(project)
val style = settings.uiFormatterStyle
val formattingOptions =
if (style == UiFormatterStyle.Custom) {
settings.customFormattingOptions
} else {
UiFormatterStyle.getStandardFormattingOptions(style)
}
return KtfmtFormattingTask(request, formattingOptions)
}

override fun getNotificationGroupId(): String = PARSING_ERROR_NOTIFICATION_GROUP
Expand All @@ -47,11 +55,11 @@ class KtfmtFormattingService : AsyncDocumentFormattingService() {

private class KtfmtFormattingTask(
private val request: AsyncFormattingRequest,
private val style: UiFormatterStyle,
private val formattingOptions: FormattingOptions,
) : FormattingTask {
override fun run() {
try {
val formattedText = format(style.formattingOptions, request.documentText)
val formattedText = format(formattingOptions, request.documentText)
request.onTextReady(formattedText)
} catch (e: FormatterException) {
request.onError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,45 +16,104 @@

package com.facebook.ktfmt.intellij

import com.facebook.ktfmt.format.Formatter
import com.facebook.ktfmt.format.FormattingOptions
import com.facebook.ktfmt.intellij.KtfmtSettings.EnabledState.Disabled
import com.facebook.ktfmt.intellij.KtfmtSettings.EnabledState.Enabled
import com.facebook.ktfmt.intellij.KtfmtSettings.EnabledState.Unknown
import com.facebook.ktfmt.intellij.UiFormatterStyle.Meta
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.BaseState
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.Service.Level.PROJECT
import com.intellij.openapi.components.SimplePersistentStateComponent
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.project.Project

@Service(PROJECT)
@State(name = "KtfmtSettings", storages = [Storage("ktfmt.xml")])
internal class KtfmtSettings : PersistentStateComponent<KtfmtSettings.State> {
private var state = State()

internal class KtfmtSettings(private val project: Project) :
SimplePersistentStateComponent<KtfmtSettings.State>(State()) {
val isUninitialized: Boolean
get() = state.enabled == Unknown
get() = state.enableKtfmt == Unknown

var uiFormatterStyle: UiFormatterStyle
get() = state.uiFormatterStyle
set(uiFormatterStyle) {
state.uiFormatterStyle = uiFormatterStyle
}

var customFormattingOptions: FormattingOptions
get() =
FormattingOptions(
state.customMaxLineLength,
state.customBlockIndent,
state.customContinuationIndent,
state.customManageTrailingCommas,
state.customRemoveUnusedImports,
)
set(customFormattingOptions) {
state.applyCustomFormattingOptions(customFormattingOptions)
}

var customMaxLineLength: Int
get() = state.customMaxLineLength
set(maxLineLength) {
state.customMaxLineLength = maxLineLength.coerceAtLeast(1)
}

var customBlockIndent: Int
get() = state.customBlockIndent
set(blockIndent) {
state.customBlockIndent = blockIndent.coerceAtLeast(1)
}

var customContinuationIndent: Int
get() = state.customContinuationIndent
set(continuationIndent) {
state.customContinuationIndent = continuationIndent.coerceAtLeast(1)
}

var customManageTrailingCommas: Boolean
get() = state.customManageTrailingCommas
set(manageTrailingCommas) {
state.customManageTrailingCommas = manageTrailingCommas
}

var customRemoveUnusedImports: Boolean
get() = state.customRemoveUnusedImports
set(removeUnusedImports) {
state.customRemoveUnusedImports = removeUnusedImports
}

var isEnabled: Boolean
get() = state.enabled == Enabled
get() = state.enableKtfmt == Enabled
set(enabled) {
setEnabled(if (enabled) Enabled else Disabled)
}

fun setEnabled(enabled: EnabledState) {
state.enabled = enabled
state.enableKtfmt = enabled
}

override fun getState(): State = state

override fun loadState(state: State) {
this.state = state
val migrated = loadOrMigrateIfNeeded(state)
super.loadState(migrated)
}

private fun loadOrMigrateIfNeeded(state: State): State {
val migrationSettings = project.service<KtfmtSettingsMigration>()

return when (val stateVersion = migrationSettings.stateVersion) {
KtfmtSettingsMigration.CURRENT_VERSION -> state
1 -> migrationSettings.migrateFromV1ToCurrent(state)
else -> {
thisLogger().error("Cannot migrate settings from $stateVersion. Using defaults.")
State()
}
}
}

internal enum class EnabledState {
Expand All @@ -63,27 +122,27 @@ internal class KtfmtSettings : PersistentStateComponent<KtfmtSettings.State> {
Disabled,
}

internal class State {
@JvmField var enabled: EnabledState = Unknown
var uiFormatterStyle: UiFormatterStyle = Meta

// enabled used to be a boolean so we use bean property methods for backwards
// compatibility
fun setEnabled(enabledStr: String?) {
enabled =
when {
enabledStr == null -> Unknown
enabledStr.toBoolean() -> Enabled
else -> Disabled
}
}
internal class State : BaseState() {
@Deprecated("Deprecated in V2. Use enableKtfmt instead.") var enabled by string()

var enableKtfmt by enum<EnabledState>(Unknown)
var uiFormatterStyle by enum<UiFormatterStyle>(Meta)

var customMaxLineLength by property(Formatter.META_FORMAT.maxWidth)
var customBlockIndent by property(Formatter.META_FORMAT.blockIndent)
var customContinuationIndent by property(Formatter.META_FORMAT.continuationIndent)
var customManageTrailingCommas by property(Formatter.META_FORMAT.manageTrailingCommas)
var customRemoveUnusedImports by property(Formatter.META_FORMAT.removeUnusedImports)

fun getEnabled(): String? =
when (enabled) {
Enabled -> "true"
Disabled -> "false"
else -> null
}
fun applyCustomFormattingOptions(formattingOptions: FormattingOptions) {
customMaxLineLength = formattingOptions.maxWidth
customBlockIndent = formattingOptions.blockIndent
customContinuationIndent = formattingOptions.continuationIndent
customManageTrailingCommas = formattingOptions.manageTrailingCommas
customRemoveUnusedImports = formattingOptions.removeUnusedImports

incrementModificationCount()
}
}

companion object {
Expand Down
Loading