Skip to content

Commit

Permalink
Add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
InSyncWithFoo committed Feb 9, 2025
1 parent 0b2cb1e commit aeb37bd
Show file tree
Hide file tree
Showing 13 changed files with 207 additions and 16 deletions.
13 changes: 13 additions & 0 deletions src/main/kotlin/insyncwithfoo/ryecharm/APIAvailability.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,23 @@ import com.intellij.openapi.util.SystemInfo
import com.intellij.platform.lsp.api.LspServerSupportProvider


/**
* Whether LSP4IJ is both installed and enabled.
*/
internal val lsp4ijIsAvailable: Boolean
get() {
val pluginID = PluginId.getId("com.redhat.devtools.lsp4ij")
return PluginManagerCore.run { isPluginInstalled(pluginID) && !isDisabled(pluginID) }
}


/**
* Whether the native client is available.
*
* According to [the docs](https://plugins.jetbrains.com/docs/intellij/language-server-protocol.html#supported-ides),
* it is part of IntelliJ IDEA Ultimate, WebStorm, PhpStorm, PyCharm Professional,
* DataSpell, RubyMine, CLion, Aqua, DataGrip, GoLand, Rider, and RustRover.
*/
@Suppress("UnstableApiUsage")
internal val lspIsAvailable by lazy {
try {
Expand All @@ -25,6 +35,9 @@ internal val lspIsAvailable by lazy {
}


/**
* Whether the IDE has WSL-specific support.
*/
@Suppress("UNUSED_EXPRESSION")
internal val wslIsSupported by lazy {

Check warning on line 42 in src/main/kotlin/insyncwithfoo/ryecharm/APIAvailability.kt

View workflow job for this annotation

GitHub Actions / Qodana for JVM

Unused symbol

Property "wslIsSupported" is never used
SystemInfo.isWindows && try {
Expand Down
12 changes: 12 additions & 0 deletions src/main/kotlin/insyncwithfoo/ryecharm/IntentionActions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,19 @@ internal fun FileDocumentManager.saveAllDocumentsAsIs() {
}


/**
* Marker for intentions that should start in write action.
*/
internal interface WriteIntentionAction : IntentionAction {
override fun startInWriteAction() = true
}


/**
* Marker for intentions that are mainly based on external processes.
*
* @see generatePreview
*/
internal interface ExternalIntentionAction : IntentionAction {

/**
Expand All @@ -38,6 +46,10 @@ internal interface ExternalIntentionAction : IntentionAction {
}


/**
* Marker for intentions that should be placed
* at the very top of the context actions panel.
*/
internal interface TopPriorityAction : PriorityAction {
override fun getPriority() = Priority.TOP
}
14 changes: 14 additions & 0 deletions src/main/kotlin/insyncwithfoo/ryecharm/JSON.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import kotlinx.serialization.json.JsonBuilder
private val lenientParser = Json { ignoreUnknownKeys = true }


/**
* Parse the given string as JSON, ignoring unknown keys and returning `null` on failure.
*/
internal inline fun <reified T> String.parseAsJSONLeniently() =
try {
lenientParser.decodeFromString<T>(this)
Expand All @@ -17,6 +20,9 @@ internal inline fun <reified T> String.parseAsJSONLeniently() =
}


/**
* Parse the given string as JSON, returning `null` on failure.
*/
internal inline fun <reified T> String.parseAsJSON() =
try {
Json.decodeFromString<T>(this)
Expand All @@ -25,6 +31,11 @@ internal inline fun <reified T> String.parseAsJSON() =
}


/**
* Parse the given string as JSON
* using the builder built with [builderAction],
* returning `null` on failure.
*/
internal inline fun <reified T> String.parseAsJSON(noinline builderAction: JsonBuilder.() -> Unit): T? {

Check warning on line 39 in src/main/kotlin/insyncwithfoo/ryecharm/JSON.kt

View workflow job for this annotation

GitHub Actions / Qodana for JVM

Unused symbol

Function "parseAsJSON" is never used
val json = Json(builderAction = builderAction)

Expand All @@ -36,5 +47,8 @@ internal inline fun <reified T> String.parseAsJSON(noinline builderAction: JsonB
}


/**
* Convert the given string to JSON.
*/
internal inline fun <reified T : Any> T.stringifyToJSON() =
Json.encodeToString<T>(this)
28 changes: 28 additions & 0 deletions src/main/kotlin/insyncwithfoo/ryecharm/KnownFiles.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,70 @@ import com.intellij.psi.PsiFile
import com.jetbrains.python.psi.PyFile


/**
* Whether the given file is `.rye/config.toml`.
*/
internal val VirtualFile.isRyeConfigToml: Boolean
get() = parent?.name == ".rye" && name == "config.toml"


/**
* Whether the given file is `pyproject.toml`.
*/
internal val VirtualFile.isPyprojectToml: Boolean
get() = name == "pyproject.toml"


/**
* Whether the given file is `uv.lock`.
*/
internal val VirtualFile.isUVLock: Boolean
get() = name == "uv.lock"


/**
* Whether the given file is `uv.toml`.
*/
internal val VirtualFile.isUVToml: Boolean
get() = name == "uv.toml"


/**
* Whether the given file is `ruff.toml` or `.ruff.toml`.
*/
internal val VirtualFile.isRuffToml: Boolean
get() = name == "ruff.toml" || name == ".ruff.toml"


// https://github.com/InSyncWithFoo/ryecharm/issues/5
/**
* Whether the given file's extension is `.rst`.
*/
private val PsiFile.isReST: Boolean
get() = virtualFile?.extension == "rst"


// https://github.com/InSyncWithFoo/ryecharm/issues/47
/**
* Whether the given file's extension is `.ipynb`.
*/
private val PsiFile.isJupyter: Boolean
get() = virtualFile?.extension == "ipynb"


// TODO: .ipynb / Allow configuring what files
/**
* Whether the given file is supported by Ruff
* *and* RyeCharm has replicated its support in some manner.
*/
internal fun VirtualFile.isSupportedByRuff(project: Project? = null): Boolean {
return extension == "py" || extension == "pyi" || extension == "pyw"
}


/**
* Shorthand for [VirtualFile.isSupportedByRuff].
*/
internal val PsiFile.isSupportedByRuff: Boolean
get() = this is PyFile && !this.isReST && !this.isJupyter
|| virtualFile?.isSupportedByRuff(project) == true
3 changes: 3 additions & 0 deletions src/main/kotlin/insyncwithfoo/ryecharm/Registry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ internal class Logging(override val parentPrefix: String) : Prefixed {
}


/**
* Thin wrapper around [Registry] to allow for .
*/
internal object RyeCharmRegistry : Prefixed {

override val parentPrefix = null
Expand Down
3 changes: 3 additions & 0 deletions src/main/kotlin/insyncwithfoo/ryecharm/RyeCharm.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package insyncwithfoo.ryecharm


/**
* Plugin-related constants.
*/
@Suppress("unused", "MemberVisibilityCanBePrivate")
internal object RyeCharm {
const val ID = "insyncwithfoo.ryecharm"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@ private fun ShellCommandSpec.toInfo() =
* in the new terminal.
*
* The information is read from bundled JSON files
* (generated using `.scripts/command_specs.py`)
* (generated using `scripts/command_specs.py`)
* and then reconstructed via [makeContentBuilder].
*
* The exact format is decided by the script.
* It is replicated here as [CommandTreeAndVersion],
* [CommandNode] and [OptionOrArgumentNode].
*/
internal class RyeCharmCommandSpecsProvider : ShellCommandSpecsProvider {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ import com.intellij.util.xmlb.XmlSerializerUtil
import insyncwithfoo.ryecharm.message


/**
* Marker for project-based/local [Configurable]s.
*
* Local configurables are different from global ones in that:
*
* * They have [Overrides].
* * They have corresponding [Project]s.
* * Their panels' names are always "Project".
* * Their panels are always children of the corresponding global panels.
*
* @see AdaptivePanel
*/
internal interface ProjectBasedConfigurable : Configurable {

val project: Project
Expand All @@ -18,6 +30,24 @@ internal interface ProjectBasedConfigurable : Configurable {
}


/**
* The base class from which concrete [Configurable] classes derive.
*
* This class implements and delegates [isModified],
* [reset] and [apply] to that of the [panel].
*
* When the user is interacting with the UI panel,
* there are three instances of the state [S],
* each belong to a different holder class:
*
* * One for the configurable which creates the panel (a subclass of this class)
* * One for the panel itself (a subclass of [AdaptivePanel])
* * One for the service (a subclass [ConfigurationService])
*
* The panel's state stores whatever values that are shown in the UI.
* The service's state stores whatever that will go to the `.xml` file.
* The configurable's state acts as a medium between them.
*/
internal abstract class PanelBasedConfigurable<S : BaseState> : Configurable {

protected abstract val state: S
Expand All @@ -36,15 +66,34 @@ internal abstract class PanelBasedConfigurable<S : BaseState> : Configurable {
afterApply()
}

/**
* Called from [apply] after [DialogPanel.apply].
*
* Responsible for synchronizing the panel's state and
* that of the service using [syncStateWithService].
*/
protected abstract fun afterApply()

/**
* Copy the state of the panel to that of the service in-place.
*/
protected fun <SS : BaseState> syncStateWithService(panelState: SS, serviceState: SS) {
XmlSerializerUtil.copyBean(panelState, serviceState)
}

}


/**
* Shorthand to extract the [Project] and [Overrides]
* from a [ProjectBasedConfigurable]:
*
* ```kotlin
* val (project, overrides) = configurable.projectAndOverrides
* // ^^^^^^^^^ Overrides?
* // ^^^^^^^ Project?
* ```
*/
internal val <S : BaseState> PanelBasedConfigurable<S>.projectAndOverrides: Pair<Project?, Overrides?>
get() = when (this is ProjectBasedConfigurable) {
true -> Pair(project, overrides)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ internal val globalUVExecutable: Path?
get() = globalUVConfigurations.executable?.toPathIfItExists() ?: UV.detectExecutable()


/**
* Change Ruff configurations in-place.
*/
internal fun Project.changeRuffConfigurations(action: RuffConfigurations.() -> Unit) {
RuffLocalService.getInstance(this).state.apply(action)
}
Expand Down
51 changes: 38 additions & 13 deletions src/main/kotlin/insyncwithfoo/ryecharm/configurations/Panels.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import kotlin.reflect.KMutableProperty0
import kotlin.reflect.full.declaredMemberProperties


/**
* Return the cells belong to this [Row].
*
* Since `RowImpl.cells` is not visible,
* this provides a workaround using reflection.
*/
private val Row.cells: List<Cell<*>>?
get() {
val properties = this::class.declaredMemberProperties
Expand All @@ -24,17 +30,6 @@ private val Row.cells: List<Cell<*>>?
}


private fun Row.rightAligningOverrideCheckbox(block: Cell<JBCheckBox>.() -> Unit) =
checkBox(message("configurations.override.label")).align(AlignX.RIGHT).apply(block)


private fun Row.toggleOtherCellsBasedOn(checkbox: Cell<JBCheckBox>) {
cells?.forEach {
it.takeIf { it !== checkbox }?.enabledIf(checkbox.selected)
}
}


private fun Overrides.toggle(element: SettingName, add: Boolean) {
when {
add -> add(element)
Expand All @@ -44,14 +39,26 @@ private fun Overrides.toggle(element: SettingName, add: Boolean) {


/**
* Generate configuration panels for [PanelBasedConfigurable]s,
* adding "Override" checkboxes for project ones.
* The base class from which concrete panel classes derive.
*
* Each such class are defined in its own file along with
* a `makeComponent` extension function that creates different panel components
* based on whether it is given [ProjectBasedConfigurable] or otherwise.
*
* Other extension functions making up that panel's subcomponents
* are also defined in the same file.
*
* @see overrideCheckbox
*/
internal abstract class AdaptivePanel<S>(val state: S, private val overrides: Overrides?, val project: Project?) {

private val projectBased: Boolean
get() = project != null

/**
* Declare an "Override" checkbox that binds itself to the given [property]'s name,
* but only if the current [AdaptivePanel] is that of a [ProjectBasedConfigurable].
*/
fun Row.overrideCheckbox(property: KMutableProperty0<*>) {
if (projectBased) {
overrideCheckbox(property.name)
Expand All @@ -71,6 +78,24 @@ internal abstract class AdaptivePanel<S>(val state: S, private val overrides: Ov
toggleOtherCellsBasedOn(checkbox)
}

private fun Row.rightAligningOverrideCheckbox(block: Cell<JBCheckBox>.() -> Unit) =
checkBox(message("configurations.override.label")).align(AlignX.RIGHT).apply(block)

/**
* Attach a callback to each cell retrieved using [Row.cells]
* that will disable/enable the cell according to the [checkbox]'s state.
*/
private fun Row.toggleOtherCellsBasedOn(checkbox: Cell<JBCheckBox>) {
cells?.forEach {
it.takeIf { it !== checkbox }?.enabledIf(checkbox.selected)
}
}

/**
* Declare an "Advanced settings" collapsible group.
*
* By default, the group is collapsed.
*/
@Suppress("DialogTitleCapitalization")
fun Panel.advancedSettingsGroup(init: Panel.() -> Unit) {
collapsibleGroup(message("configurations.groups.advanced"), init = init)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,11 @@ import com.intellij.openapi.components.BaseState
import com.intellij.openapi.components.SimplePersistentStateComponent


/**
* The base class from which concrete services derive.
*
* Used by various classes and functions as an abstraction.
*
* @see SimplePersistentStateComponent
*/
internal open class ConfigurationService<S : BaseState>(state: S) : SimplePersistentStateComponent<S>(state)
Loading

0 comments on commit aeb37bd

Please sign in to comment.