Skip to content

Commit 9f2ab2c

Browse files
committed
refactor: allow stepping back and forth in template segments without losing typed values
1 parent b9e411e commit 9f2ab2c

File tree

1 file changed

+53
-3
lines changed

1 file changed

+53
-3
lines changed

src/main/kotlin/com/github/lppedd/cc/liveTemplate/CCTemplateEditingListener.kt

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.github.lppedd.cc.lookupElement.TemplateSegment
66
import com.intellij.codeInsight.template.Template
77
import com.intellij.codeInsight.template.TemplateEditingAdapter
88
import com.intellij.codeInsight.template.impl.TemplateState
9+
import com.intellij.codeInsight.template.impl.TemplateStateBase
910
import com.intellij.openapi.application.runWriteAction
1011
import com.intellij.openapi.command.WriteCommandAction
1112
import com.intellij.openapi.diagnostic.logger
@@ -17,6 +18,14 @@ import kotlin.math.min
1718
* @author Edoardo Luppi
1819
*/
1920
internal class CCTemplateEditingListener : TemplateEditingAdapter() {
21+
private class MyHashMap : HashMap<String, String> {
22+
constructor() : super()
23+
constructor(map: Map<String, String>) : super(map)
24+
25+
override fun containsKey(key: String): Boolean =
26+
false
27+
}
28+
2029
private companion object {
2130
private val logger = logger<CCTemplateEditingListener>()
2231
}
@@ -41,6 +50,30 @@ internal class CCTemplateEditingListener : TemplateEditingAdapter() {
4150
return
4251
}
4352

53+
// This is a workaround to restore the old (cannot remember how old tho) template behavior,
54+
// where stepping to a previous template segment would not erase the currently inputted value.
55+
// To achieve this, we store the currently inputted value for the segment we are stepping
56+
// away from into the segments' predefined values map.
57+
//
58+
// MyHashMap is used to trick 'TemplateState.checkIfTabStop' in believing the segment
59+
// we are stepping into does not have a predefined value. Otherwise, the template engine
60+
// simply skips that segment.
61+
var predefinedValues = templateState.getPredefinedVariableValues()
62+
63+
if (predefinedValues !is MyHashMap) {
64+
predefinedValues = predefinedValues?.let(::MyHashMap) ?: MyHashMap()
65+
templateState.setPredefinedVariableValues(predefinedValues)
66+
}
67+
68+
if (newIndex < oldIndex) {
69+
val segmentName = template.getSegmentName(oldIndex)
70+
val segmentRange = templateState.getSegmentRange(oldIndex)
71+
predefinedValues[segmentName] = templateState.editor.document.getText(segmentRange)
72+
} else {
73+
val segmentName = template.getSegmentName(newIndex)
74+
predefinedValues.remove(segmentName)
75+
}
76+
4477
if (oldIndex == TemplateSegment.BodyOrFooterType && newIndex > oldIndex) {
4578
if (templateState.getSegmentRange(TemplateSegment.BodyOrFooterType).isEmpty) {
4679
deleteFooterValue(templateState)
@@ -50,9 +83,8 @@ internal class CCTemplateEditingListener : TemplateEditingAdapter() {
5083
}
5184

5285
val newOffset = templateState.getSegmentRange(newIndex).startOffset
53-
val editor = templateState.editor
54-
editor.moveCaretToOffset(newOffset)
55-
editor.scheduleAutoPopup()
86+
templateState.editor.moveCaretToOffset(newOffset)
87+
templateState.editor.scheduleAutoPopup()
5688
}
5789

5890
override fun beforeTemplateFinished(templateState: TemplateState, template: Template) {
@@ -111,6 +143,24 @@ internal class CCTemplateEditingListener : TemplateEditingAdapter() {
111143
}
112144
}
113145

146+
@Suppress("UNCHECKED_CAST")
147+
private fun TemplateStateBase.getPredefinedVariableValues(): MutableMap<String, String>? =
148+
logger.runAndLogError(null) {
149+
TemplateStateBase::class.java.getDeclaredMethod("getPredefinedVariableValues").let {
150+
it.isAccessible = true
151+
it.invoke(this) as MutableMap<String, String>?
152+
}
153+
}
154+
155+
private fun TemplateStateBase.setPredefinedVariableValues(map: MutableMap<String, String>?) {
156+
logger.runAndLogError(Unit) {
157+
TemplateStateBase::class.java.getDeclaredMethod("setPredefinedVariableValues", Map::class.java).let {
158+
it.isAccessible = true
159+
it.invoke(this, map)
160+
}
161+
}
162+
}
163+
114164
@Compatibility(
115165
keepForHistoricReasons = true,
116166
minVersion = "202.4357.23",

0 commit comments

Comments
 (0)