@@ -4,30 +4,131 @@ import com.intellij.openapi.util.TextRange
44import com.intellij.psi.LiteralTextEscaper
55import com.intellij.psi.PsiLanguageInjectionHost
66import org.nixos.idea.psi.impl.AbstractNixString
7- import org.nixos.idea.util.NixIndStringUtil
8- import org.nixos.idea.util.NixStringUtil
97
108class NixStringLiteralEscaper (host : AbstractNixString ) : LiteralTextEscaper<PsiLanguageInjectionHost>(host) {
119
1210 override fun isOneLine (): Boolean = false
1311
12+ private var outSourceOffsets: IntArray? = null
13+
14+ override fun getRelevantTextRange (): TextRange {
15+ if (myHost.textLength <= 4 ) return TextRange .EMPTY_RANGE
16+ return TextRange .create(2 , myHost.textLength - 2 )
17+ }
18+
1419 override fun decode (rangeInsideHost : TextRange , outChars : StringBuilder ): Boolean {
20+ // only indented strings supported for now
21+ if (myHost !is NixIndString ) return false
22+
1523 val subText: String = rangeInsideHost.substring(myHost.text)
16- if (myHost is NixIndString ) {
17- outChars.append( NixIndStringUtil .escape(subText))
18- } else {
19- NixStringUtil .escape( outChars, subText )
20- }
21- return true
24+
25+
26+ val array = IntArray (subText.length + 1 )
27+ val success = unescapeAndDecode(subText, outChars, array, interpolations = false )
28+ outSourceOffsets = array
29+ return success
2230 }
2331
2432 override fun getOffsetInHost (offsetInDecoded : Int , rangeInsideHost : TextRange ): Int {
25- // TODO: Implement proper String back-feed support.
26- // this involves keeping track of text offsets between decoded
27- // and encoded Nix text. See how Terraform does it here:
28- // https://github.com/JetBrains/intellij-plugins/blob/master/terraform/src/org/intellij/terraform/hcl/psi/impl/HCLStringLiteralTextEscaper.kt
29- val offsetInHost = offsetInDecoded + rangeInsideHost.startOffset
30- return (offsetInHost).coerceIn(rangeInsideHost.startOffset.. rangeInsideHost.endOffset)
33+ val offsets = outSourceOffsets ? : throw IllegalStateException (" #decode was not called" )
34+ val result = if (offsetInDecoded < offsets.size) offsets[offsetInDecoded] else - 1
35+ println (" gotOffsetInHost decoded=${offsetInDecoded} rangeInsideHost=${rangeInsideHost} result=$result " )
36+ return result.coerceIn(2 .. rangeInsideHost.length) + rangeInsideHost.startOffset
37+ }
38+
39+ companion object {
40+ fun unescapeAndDecode (
41+ chars : String ,
42+ outChars : StringBuilder ,
43+ sourceOffsets : IntArray? ,
44+ interpolations : Boolean
45+ ): Boolean {
46+ assert (sourceOffsets == null || sourceOffsets.size == chars.length + 1 )
47+
48+ var index = 0
49+ val outOffset = outChars.length
50+ var braces = 0
51+
52+
53+ while (index < chars.length) {
54+ fun updateOffsets (index : Int ) {
55+ if (sourceOffsets != null ) {
56+ sourceOffsets[outChars.length - outOffset] = index - 1
57+ sourceOffsets[outChars.length - outOffset + 1 ] = index
58+ }
59+ }
60+ var c = chars[index++ ]
61+
62+ updateOffsets(index)
63+
64+
65+ if (braces > 0 ) {
66+ if (c == ' {' ) braces++
67+ else if (c == ' }' ) braces--
68+ outChars.append(c)
69+ continue
70+ }
71+
72+ if (c == ' \' ' ) {
73+ if (index == chars.length) return false
74+ c = chars[index++ ]
75+
76+ if (c != ' \' ' ) {
77+ // if what follows isn't another ' then we are not escaping anything,
78+ // so we can continue
79+ outChars.append(" \' " )
80+ updateOffsets(index - 1 )
81+ outChars.append(c)
82+ continue
83+ }
84+
85+ if (index == chars.length) return false
86+ c = chars[index++ ]
87+
88+ when (c) {
89+ // '' can be escaped by prefixing it with ', i.e., '''.
90+ ' \' ' -> {
91+ outChars.append(" \' " )
92+ updateOffsets(index - 1 )
93+ outChars.append(c)
94+ }
95+ // $ can be escaped by prefixing it with '' (that is, two single quotes), i.e., ''$.
96+ ' $' -> outChars.append(c)
97+ // Linefeed, carriage-return and tab characters can
98+ // be written as ''\n, ''\r, ''\t, and ''\ escapes any other character.
99+ ' a' -> outChars.append(0x07 .toChar())
100+ ' b' -> outChars.append(' \b ' )
101+ ' f' -> outChars.append(0x0c .toChar())
102+ ' n' -> outChars.append(' \n ' )
103+ ' t' -> outChars.append(' \t ' )
104+ ' r' -> outChars.append(' \r ' )
105+ ' v' -> outChars.append(0x0b .toChar())
106+ else -> return false
107+ }
108+ }
109+
110+ // // $ removes any special meaning from the following $.
111+ // if (c == '$') {
112+ // if (index == chars.length) return false
113+ // c = chars[index++]
114+ // if (c != '$') {
115+ // // if what follows isn't another ' then we are not escaping anything,
116+ // // so we can continue
117+ // outChars.append('$')
118+ // updateOffsets(index - 1)
119+ // outChars.append(c)
120+ // continue
121+ // }
122+ // // what here??
123+ // }
124+
125+
126+ if (sourceOffsets != null ) {
127+ sourceOffsets[outChars.length - outOffset] = index
128+ }
129+ }
130+ return true
131+ }
31132 }
32133
33134}
0 commit comments