|
| 1 | +package cc.unitmesh.devti.settings |
| 2 | + |
| 3 | +import cc.unitmesh.devti.AutoDevBundle |
| 4 | +import com.intellij.openapi.ui.ComboBox |
| 5 | +import com.intellij.ui.components.JBPasswordField |
| 6 | +import com.intellij.ui.components.JBTextField |
| 7 | +import java.awt.event.ItemEvent |
| 8 | +import kotlin.properties.PropertyDelegateProvider |
| 9 | +import kotlin.properties.ReadOnlyProperty |
| 10 | +import kotlin.reflect.KProperty |
| 11 | + |
| 12 | + |
| 13 | +/** |
| 14 | + * A simple version of reactive delegate |
| 15 | + * |
| 16 | + * ```kotlin |
| 17 | + * var s by Reactive("hello") { |
| 18 | + * println("s changed to $it") |
| 19 | + * } |
| 20 | + * s = "world" // println "s changed to world" |
| 21 | + * ``` |
| 22 | + */ |
| 23 | +class Reactive<V>(var value: V, val onChange: (V) -> Unit) |
| 24 | + |
| 25 | +operator fun <V> Reactive<V>.setValue(thisRef: Any?, property: KProperty<*>, value: V) { |
| 26 | + if (this.value == value) return |
| 27 | + this.value = value |
| 28 | + onChange(value) |
| 29 | +} |
| 30 | + |
| 31 | +operator fun <V> Reactive<V>.getValue(thisRef: Any?, property: KProperty<*>): V { |
| 32 | + return this.value |
| 33 | +} |
| 34 | + |
| 35 | +fun ReactiveTextField(param: LLMParam, initBlock: JBTextField.(LLMParam) -> Unit = {}): JBTextField { |
| 36 | + val component = JBTextField(param.value) |
| 37 | + val reactive by Reactive(param) { |
| 38 | + component.text = param.value |
| 39 | + } |
| 40 | + |
| 41 | + component.initBlock(reactive) |
| 42 | + |
| 43 | + component.document.addUndoableEditListener { |
| 44 | + param.value = component.text |
| 45 | + } |
| 46 | + return component |
| 47 | +} |
| 48 | + |
| 49 | +fun ReactivePasswordField(param: LLMParam, initBlock: JBPasswordField.(LLMParam) -> Unit = {}): JBPasswordField { |
| 50 | + val component = JBPasswordField().apply { |
| 51 | + text = param.value |
| 52 | + } |
| 53 | + val reactive = Reactive(param) { |
| 54 | + component.text = it.value |
| 55 | + } |
| 56 | + |
| 57 | + component.initBlock(reactive.value) |
| 58 | + component.document.addUndoableEditListener { |
| 59 | + if (component.text == param.value) return@addUndoableEditListener |
| 60 | + reactive.value.value = component.text |
| 61 | + } |
| 62 | + |
| 63 | + return component |
| 64 | +} |
| 65 | + |
| 66 | +fun ReactiveComboBox(param: LLMParam, initBlock: ComboBox<String>.(LLMParam) -> Unit = {}): ComboBox<String> { |
| 67 | + val component = ComboBox(param.items.toTypedArray()).apply { |
| 68 | + selectedItem = param.value |
| 69 | + } |
| 70 | + val reactive by Reactive(param) { |
| 71 | + component.selectedItem = it.value |
| 72 | + } |
| 73 | + |
| 74 | + component.initBlock(reactive) |
| 75 | + component.addItemListener { |
| 76 | + if (it.stateChange == ItemEvent.SELECTED) { |
| 77 | + println("item changed to ${component.selectedItem}") |
| 78 | + reactive.value = component.selectedItem as String |
| 79 | + } |
| 80 | + } |
| 81 | + return component |
| 82 | +} |
| 83 | + |
| 84 | + |
| 85 | +/** |
| 86 | + * |
| 87 | + * A LLMParam is a setting for the LLMSettingsComponent. |
| 88 | + * |
| 89 | + * Adding a LLM Param: |
| 90 | + * |
| 91 | + * - Step 1. add label to [AutoDevBundle] with key `settings.<yourName>` |
| 92 | + * - Step 2. define a variable named yourName. in this example, it's `openAIKey` |
| 93 | + * in the `creating` block, you can use one of the factory functions: |
| 94 | + * [LLMParam.Editable], [LLMParam.Password], [LLMParam.ComboBox] |
| 95 | + * ```kotlin |
| 96 | + * val openAIKey by LLMParam.creating { |
| 97 | + * Editable(service.getOpenAIKey()) |
| 98 | + * } |
| 99 | + * ``` |
| 100 | + * |
| 101 | + * |
| 102 | + * @param label the label of the setting, will automatically get from bundle resource [AutoDevBundle], named by `settings.${property.name}` |
| 103 | + * @param value the value of the setting, default from bundle resource [AutoDevBundle], named `settings.${property.name}.default` |
| 104 | + * @param type the type of the setting, default is [ParamType.Text], can be [ParamType.Password] or [ParamType.ComboBox] |
| 105 | + * @param isEditable whether the setting is editable, if is not editable, user can't change the value on UI |
| 106 | + * @param items if [type] is [ParamType.ComboBox], this field will be used to set the items of the combo box |
| 107 | + */ |
| 108 | +class LLMParam( |
| 109 | + value: String = "", |
| 110 | + var label: String = "", |
| 111 | + val isEditable: Boolean = true, |
| 112 | + val type: ParamType = ParamType.Text, |
| 113 | + var items: List<String> = emptyList(), |
| 114 | + var visible: Boolean = true, |
| 115 | +) { |
| 116 | + enum class ParamType { |
| 117 | + Text, Password, ComboBox, Separator |
| 118 | + } |
| 119 | + |
| 120 | + |
| 121 | + private var onChange: (LLMParam.(String) -> Unit)? = null |
| 122 | + |
| 123 | + |
| 124 | + var value: String = value |
| 125 | + set(newValue) { |
| 126 | + val changed = field != newValue |
| 127 | + field = newValue |
| 128 | + if (changed) { |
| 129 | + println("value changed $newValue $value") |
| 130 | + onChange?.invoke(this, newValue) |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + companion object { |
| 135 | + private val bundle = AutoDevBundle |
| 136 | + |
| 137 | + /** |
| 138 | + * @param block a block to create a LLMParam, will be called only once |
| 139 | + * |
| 140 | + * will set [label] and [value] to the value defined in config file [AutoDevBundle] if they are empty |
| 141 | + */ |
| 142 | + fun creating(onChange: LLMParam.(String) -> Unit = {}, block: Companion.() -> LLMParam) = |
| 143 | + PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, LLMParam>> { _, _ -> |
| 144 | + object : ReadOnlyProperty<Any?, LLMParam> { |
| 145 | + private var param: LLMParam? = null |
| 146 | + override fun getValue(thisRef: Any?, property: KProperty<*>): LLMParam { |
| 147 | + return param ?: this@Companion.block().apply { |
| 148 | + if (label.isEmpty()) { |
| 149 | + val key = "settings.${property.name}" |
| 150 | + label = runCatching { bundle.getMessage(key) }.getOrElse { |
| 151 | + "WARNNING-KEY:add key: settings.$key to AutoDevBundle.properties" |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + this.onChange = onChange |
| 156 | + param = this |
| 157 | + } |
| 158 | + } |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + // factory functions to create LLMParam |
| 163 | + fun Editable(value: String = "") = LLMParam(value = value) |
| 164 | + fun Password(password: String = "") = LLMParam(value = password, type = ParamType.Password) |
| 165 | + |
| 166 | + fun ComboBox(value: String, items: List<String>) = |
| 167 | + LLMParam(value = value, type = ParamType.ComboBox, items = items.toList()) |
| 168 | + |
| 169 | + fun Separator() = LLMParam(type = ParamType.Separator) |
| 170 | + } |
| 171 | +} |
| 172 | + |
0 commit comments