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

Vals and vars #483

Merged
merged 3 commits into from
Oct 22, 2023
Merged
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
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,25 @@ See [Plugin Signing](https://plugins.jetbrains.com/docs/intellij/plugin-signing.

### 🧪 Quality assurance
```bash
$ gradlew test # Run tests
$ gradlew test --tests X # Run tests in class X (package name optional)
$ gradlew check # Run tests and static analysis
$ gradlew runPluginVerifier # Check for compatibility issues
$ gradlew test # Run tests (and collect coverage)
$ gradlew test --tests X # Run tests in class X (package name optional)
$ gradlew test -Pkotest.tags="X" # Run tests with `NamedTag` X (also supports not (!), and (&), or (|))
$ gradlew koverHtmlReport # Create HTML coverage report for previous test run
$ gradlew check # Run tests and static analysis
$ gradlew runPluginVerifier # Check for compatibility issues
```

#### 🏷️ Filtering tests
[Kotest tests can be tagged](https://kotest.io/docs/framework/tags.html) to allow selectively running tests.
Tag an entire test class by adding `tags(...)` to the class definition, or tag an individual test `context` by
writing `context("foo").config(tags = setOf(...)) {`.
It is not possible to tag an individual `test` due to limitations in Kotest.

To run only one `context` in some test class `X`, prefix the `context`'s name with `f:` and run with `--tests X`.
The prefix `f:` filters out other `context`s in that test class, and `--tests X` filters out other test classes.
Alternatively, tag the desired `context` with the `Focus` tag and run with `-Pkotest.tags="Focus"` to filter by that
tag.

### 📚 Documentation
```bash
$ gradlew dokkaHtml # Generate documentation
Expand Down
8 changes: 5 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ dependencies {

testImplementation("org.assertj:assertj-swing-junit:${properties("assertjSwingVersion")}")
testRuntimeOnly("org.junit.platform:junit-platform-runner:${properties("junitRunnerVersion")}")
testImplementation("org.junit.jupiter:junit-jupiter-api:${properties("junitVersion")}")
testImplementation("org.junit.jupiter:junit-jupiter-engine:${properties("junitVersion")}")
testImplementation("org.junit.vintage:junit-vintage-engine:${properties("junitVersion")}")
testImplementation("io.kotest:kotest-assertions-core:${properties("kotestVersion")}")
testImplementation("io.kotest:kotest-framework-datatest:${properties("kotestVersion")}")
Expand Down Expand Up @@ -104,9 +102,13 @@ tasks {
// Tests/coverage
test {
systemProperty("java.awt.headless", "false")
if (project.hasProperty("kotest.tags")) systemProperty("kotest.tags", project.findProperty("kotest.tags")!!)

useJUnitPlatform {
includeEngines("junit-vintage", "junit-jupiter", "kotest")
if (!project.hasProperty("kotest.tags"))
includeEngines("junit-vintage")

includeEngines("kotest")
}

testLogging {
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/fwdekker/randomness/Bundle.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ object Bundle {
* @throws java.util.MissingFormatArgumentException if [args] has fewer arguments than required for [format]
*/
fun String.matchesFormat(format: String, vararg args: String) =
Regex("%[0-9]+\\\$[Ss]").findAll(format)
Regex("%[0-9]+\\\$[Ssd]").findAll(format)
.toList()
.reversed()
.fold(format) { acc, match ->
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/fwdekker/randomness/Scheme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import kotlin.random.Random
/**
* A scheme is a [State] that is also a configurable random number generator.
*
* Schemes can additionally use [DecoratorScheme]s to extend their functionality.
* Schemes may use [DecoratorScheme]s to extend their functionality.
*/
abstract class Scheme : State() {
/**
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/fwdekker/randomness/SchemeEditor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,5 @@ abstract class SchemeEditor<S : Scheme>(val scheme: S) : Disposable {
/**
* Disposes this editor's resources.
*/
override fun dispose() = Disposer.dispose(this)
override fun dispose() = decoratorEditors.forEach { Disposer.dispose(it) }
}
42 changes: 25 additions & 17 deletions src/main/kotlin/com/fwdekker/randomness/Settings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.intellij.openapi.components.SettingsCategory
import com.intellij.openapi.components.Storage
import com.intellij.openapi.components.service
import com.intellij.util.xmlb.XmlSerializer
import com.intellij.util.xmlb.annotations.OptionTag
import com.intellij.util.xmlb.annotations.Transient
import org.jdom.Element
import java.lang.module.ModuleDescriptor
Expand All @@ -22,7 +23,8 @@ import com.intellij.openapi.components.State as JBState
*/
data class Settings(
var version: String = CURRENT_VERSION,
var templateList: TemplateList = TemplateList(),
@OptionTag
val templateList: TemplateList = TemplateList(),
) : State() {
/**
* @see TemplateList.templates
Expand All @@ -31,27 +33,34 @@ data class Settings(
val templates: MutableList<Template> get() = templateList.templates


override fun doValidate() = templateList.doValidate()

override fun deepCopy(retainUuid: Boolean) =
copy(templateList = templateList.deepCopy(retainUuid = retainUuid)).deepCopyTransient(retainUuid)
init {
applyContext(this)
}

override fun copyFrom(other: State) {
require(other is Settings) { "Cannot copy from different type." }

this.templateList.copyFrom(other.templateList)
copyFromTransient(other)
override fun applyContext(context: Box<Settings>) {
super.applyContext(context)
templateList.applyContext(context)
}


override fun doValidate() = templateList.doValidate()

override fun deepCopy(retainUuid: Boolean) =
copy(templateList = templateList.deepCopy(retainUuid = retainUuid))
.deepCopyTransient(retainUuid)
.also { it.applyContext(it) }


/**
* Holds constants.
*/
companion object {
/**
* The persistent [Settings] instance.
*/
val DEFAULT: Settings by lazy { service<PersistentSettings>().settings }
val DEFAULT: Settings
get() = service<PersistentSettings>().settings
}
}

Expand All @@ -70,11 +79,11 @@ data class Settings(
)
class PersistentSettings : PersistentStateComponent<Element> {
/**
* The persistent settings instance.
* The [Settings] that should be persisted.
*
* @see Settings.DEFAULT Preferred method of accessing the persistent settings instance.
*/
val settings = Settings()
var settings = Settings()


/**
Expand All @@ -83,12 +92,11 @@ class PersistentSettings : PersistentStateComponent<Element> {
override fun getState(): Element = XmlSerializer.serialize(settings)

/**
* Deserializes [element] into a [Settings] instance, which is then copied into the [settings] instance.
*
* @see TemplateList.copyFrom
* Deserializes [element] into a [Settings] instance, which is then stored in [settings].
*/
override fun loadState(element: Element) =
settings.copyFrom(XmlSerializer.deserialize(upgrade(element), Settings::class.java))
override fun loadState(element: Element) {
settings = XmlSerializer.deserialize(upgrade(element), Settings::class.java)
}


/**
Expand Down
44 changes: 12 additions & 32 deletions src/main/kotlin/com/fwdekker/randomness/State.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package com.fwdekker.randomness

import com.fasterxml.uuid.Generators
import com.intellij.util.xmlb.XmlSerializerUtil
import com.intellij.util.xmlb.annotations.Transient
import kotlin.random.Random
import kotlin.random.asJavaRandom


/**
* A state holds variables that can be configured.
* A state holds variables that can be configured, validated, copied, and loaded.
*
* In a [State], fields are typically mutable, because the user can change the field. However, fields that themselves
* also contain data (e.g. a [Collection] or a [DecoratorScheme]) should be stored as immutable references (but may
* themselves be mutable). For example, instead of having the field `var list: List<String>`, a [State] should have the
* field `val list: MutableList<String>`. This reflects the typical case in which the user does not change the reference
* to the object, but changes the properties inside the object. And, more importantly, means that nested a
* [SchemeEditor] can share a single unchanging reference to a [State] with its nested [SchemeEditor]s.
*/
abstract class State {
/**
Expand Down Expand Up @@ -54,13 +60,13 @@ abstract class State {
*
* Fields annotated with [Transient] are shallow-copied.
*
* @see deepCopyTransient utility function for subclasses that want to implement `deepCopy`
* @see deepCopyTransient utility function for subclasses that want to implement [deepCopy]
*/
abstract fun deepCopy(retainUuid: Boolean = false): State

/**
* When invoked by the instance `this` as `self.deepCopyTransient()`, this method copies [Transient] fields from
* `this` to `self`, and returns `self`.
* When invoked by the instance `this` on (another) instance `self` as `self.deepCopyTransient()`, this method
* copies [Transient] fields from `this` to `self`, and returns `self`.
*
* @see deepCopy
*/
Expand All @@ -69,34 +75,8 @@ abstract class State {
val thiz: State = this@State

if (retainUuid) self.uuid = thiz.uuid
self.applyContext(thiz.context.copy())
self.applyContext(thiz.context.copy()) // Copies the [Box], not the context itself

return self
}

/**
* Copies [other] into `this`.
*
* Works by shallow-copying a [deepCopy] of [other] into `this`. [Transient] fields are shallow-copied directly from
* [other] instead.
*
* Implementations may choose to shallow-copy additional fields directly from [other].
*
* @param other the state to copy into `this`; should be a (sub)class of this state
* @see copyFromTransient utility function for subclasses that want to implement `copyFrom`
*/
open fun copyFrom(other: State) {
XmlSerializerUtil.copyBean(other.deepCopy(retainUuid = true), this)
copyFromTransient(other)
}

/**
* Copies basic [Transient] fields from [other] into `this`.
*
* Typically used by subclasses to help implement [copyFrom].
*/
protected fun copyFromTransient(other: State) {
this.uuid = other.uuid
this.applyContext(other.context.copy())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ data class Template(
WordScheme::class,
]
)
var schemes: MutableList<Scheme> = DEFAULT_SCHEMES,
val schemes: MutableList<Scheme> = DEFAULT_SCHEMES,
val arrayDecorator: ArrayDecorator = DEFAULT_ARRAY_DECORATOR,
) : Scheme() {
override val typeIcon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ open class TemplateActionLoader(
newList: List<Template>,
actionManager: ActionManager = ActionManager.getInstance(),
) {
oldList.filterNot { it in newList }.forEach { unregisterAction(actionManager, it) }
val newUuidList = newList.map { it.uuid }
oldList.filterNot { it.uuid in newUuidList }.forEach { unregisterAction(actionManager, it) }
newList.forEach { registerAction(actionManager, it) }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import com.fwdekker.randomness.Timely.generateTimely
import com.fwdekker.randomness.array.ArrayDecorator
import com.fwdekker.randomness.array.ArrayDecoratorEditor
import com.fwdekker.randomness.ui.PreviewPanel
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.ActionGroup
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.util.Disposer
import java.awt.BorderLayout
import javax.swing.JPanel

Expand Down Expand Up @@ -128,9 +130,13 @@ class TemplateInsertAction(
*
* @param arrayDecorator the decorator being edited in this configurable
*/
inner class ArrayDecoratorConfigurable(arrayDecorator: ArrayDecorator) : Configurable {
private val editor = ArrayDecoratorEditor(arrayDecorator, embedded = true)
private val previewPanel = PreviewPanel { template.deepCopy().also { it.arrayDecorator.enabled = true } }
inner class ArrayDecoratorConfigurable(arrayDecorator: ArrayDecorator) : Configurable, Disposable {
private val editor =
ArrayDecoratorEditor(arrayDecorator, embedded = true)
.also { Disposer.register(this, it) }
private val previewPanel =
PreviewPanel { template.deepCopy().also { it.arrayDecorator.enabled = true } }
.also { Disposer.register(this, it) }


init {
Expand Down Expand Up @@ -170,12 +176,14 @@ class TemplateInsertAction(
override fun getDisplayName() = text

/**
* Disposes the [editor] and [previewPanel].
* Recursively disposes this configurable's resources.
*/
override fun disposeUIResources() {
editor.dispose()
previewPanel.dispose()
}
override fun disposeUIResources() = Disposer.dispose(this)

/**
* Non-recursively disposes this configurable's resources.
*/
override fun dispose() = Unit
}
}

Expand All @@ -201,6 +209,6 @@ class TemplateSettingsAction(private val template: Template? = null) : AnAction(
override fun actionPerformed(event: AnActionEvent) =
ShowSettingsUtil.getInstance()
.showSettingsDialog(event.project, TemplateListConfigurable::class.java) { configurable ->
configurable?.also { it.templateToSelect = template?.uuid }
configurable?.also { it.schemeToSelect = template?.uuid }
}
}
Loading