Skip to content

Commit

Permalink
support indent in fragment editor
Browse files Browse the repository at this point in the history
  • Loading branch information
cottand committed Jul 7, 2024
1 parent f97fee3 commit 43664e2
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 20 deletions.
51 changes: 36 additions & 15 deletions src/main/java/org/nixos/idea/psi/NixStringLiteralEscaper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,25 @@ class NixStringLiteralEscaper(host: AbstractNixString) : LiteralTextEscaper<PsiL
override fun isOneLine(): Boolean = false

private var outSourceOffsets: IntArray? = null
private var minIndent: Int? = 0

override fun getRelevantTextRange(): TextRange {
if (myHost.textLength <= 4) return TextRange.EMPTY_RANGE
return TextRange.create(2, myHost.textLength - 2)
}

override fun decode(rangeInsideHost: TextRange, outChars: StringBuilder): Boolean {
// TODO only indented strings supported for now
// TODO issue #81 only indented strings supported for now
// single line strings require a new decode function because
// it uses different escaping mechanisms
if (myHost !is NixIndString) return false

val subText: String = rangeInsideHost.substring(myHost.text)
val array = IntArray(subText.length + 1)
val success = unescapeAndDecode(subText, outChars, array)
val foundIndent = unescapeAndDecode(subText, outChars, array)
outSourceOffsets = array
return success
minIndent = foundIndent ?: return false
return true
}

override fun getOffsetInHost(offsetInDecoded: Int, rangeInsideHost: TextRange): Int {
Expand All @@ -36,14 +38,22 @@ class NixStringLiteralEscaper(host: AbstractNixString) : LiteralTextEscaper<PsiL
}

companion object {
// this function does not consider interpolations so that
// they do appear in the guest language and remain when we end up converting back to Nix
fun unescapeAndDecode(chars: String, outChars: StringBuilder, sourceOffsets: IntArray?): Boolean {
/**
* Does not consider interpolations so that
* they do appear in the guest language and remain when we end up converting back to Nix.
*
* @returns the minIndent of the string if successful, or null if unsuccessful.
*/
fun unescapeAndDecode(chars: String, outChars: StringBuilder, sourceOffsets: IntArray?): Int? {
assert(sourceOffsets == null || sourceOffsets.size == chars.length + 1)

var index = 0
val outOffset = outChars.length
var braces = 0
var indentSoFar = 0
val minIndent = chars.lines()
.filterNot { it.isEmpty() }
.minOfOrNull { it.takeWhile(Char::isWhitespace).count() } ?: 0


while (index < chars.length) {
Expand All @@ -53,8 +63,8 @@ class NixStringLiteralEscaper(host: AbstractNixString) : LiteralTextEscaper<PsiL
sourceOffsets[outChars.length - outOffset + 1] = index
}
}
var c = chars[index++]

var c = chars[index++]
updateOffsets(index)


Expand All @@ -65,8 +75,18 @@ class NixStringLiteralEscaper(host: AbstractNixString) : LiteralTextEscaper<PsiL
continue
}

if (c == '\n' && index < chars.length - 1) {
// we know that the next n chars are going to be whitespace indent
index += minIndent
outChars.append(c)
if (sourceOffsets != null) {
sourceOffsets[outChars.length - outOffset] = index
}
continue
}

if (c == '\'') {
if (index == chars.length) return false
if (index == chars.length) return null
c = chars[index++]

if (c != '\'') {
Expand All @@ -78,10 +98,10 @@ class NixStringLiteralEscaper(host: AbstractNixString) : LiteralTextEscaper<PsiL
continue
}

if (index == chars.length) return false
if (index == chars.length) return null
c = chars[index++]

when(c) {
when (c) {
// '' can be escaped by prefixing it with ', i.e., '''.
'\'' -> {
outChars.append("\'")
Expand All @@ -91,9 +111,9 @@ class NixStringLiteralEscaper(host: AbstractNixString) : LiteralTextEscaper<PsiL
// $ can be escaped by prefixing it with '' (that is, two single quotes), i.e., ''$.
'$' -> outChars.append(c)
'\\' -> {
if (index == chars.length) return false
if (index == chars.length) return null
c = chars[index++]
when(c) {
when (c) {
// Linefeed, carriage-return and tab characters can
// be written as ''\n, ''\r, ''\t, and ''\ escapes any other character.
'a' -> outChars.append(0x07.toChar())
Expand All @@ -103,10 +123,11 @@ class NixStringLiteralEscaper(host: AbstractNixString) : LiteralTextEscaper<PsiL
't' -> outChars.append('\t')
'r' -> outChars.append('\r')
'v' -> outChars.append(0x0b.toChar())
else -> return false
else -> return null
}
}
else -> return false

else -> return null
}
if (sourceOffsets != null) {
sourceOffsets[outChars.length - outOffset] = index
Expand All @@ -116,7 +137,7 @@ class NixStringLiteralEscaper(host: AbstractNixString) : LiteralTextEscaper<PsiL

outChars.append(c)
}
return true
return minIndent
}
}

Expand Down
26 changes: 21 additions & 5 deletions src/main/java/org/nixos/idea/psi/impl/AbstractNixString.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,35 @@ import org.nixos.idea.psi.NixString
import org.nixos.idea.psi.NixStringLiteralEscaper


abstract class AbstractNixString(private val astNode: ASTNode) : PsiLanguageInjectionHost, AbstractNixPsiElement(astNode),NixString {
abstract class AbstractNixString(private val astNode: ASTNode) : PsiLanguageInjectionHost,
AbstractNixPsiElement(astNode), NixString {

override fun isValidHost() = true

override fun updateText(s: String): NixString {
// TODO also support single-line strings
// TODO issue #81 also support single-line strings
if (this !is NixIndString) {
LOG.info("not a nix ind string")
return this
}
val withoutQuotes = s.substring(2..(s.lastIndex - 2))
(astNode.firstChildNode.treeNext.firstChildNode as? LeafPsiElement)
?.replaceWithText(withoutQuotes)
val originalNode = astNode.firstChildNode.treeNext.firstChildNode as? LeafPsiElement
val minIndentInOriginal = originalNode?.text?.lines()
?.filterNot { it.isEmpty() }
?.minOfOrNull { it.takeWhile(Char::isWhitespace).count() } ?: 0

val leadingSpace = buildString { repeat(minIndentInOriginal) { append(' ') } }

val withoutQuotesWithIndent = s
// remove quotes
.substring(2..(s.lastIndex - 2))
// restore indent
.lines()
.withIndex()
.joinToString(separator = System.lineSeparator()) { (index, line) ->
if (index != 0) leadingSpace + line else line
}

originalNode?.replaceWithText(withoutQuotesWithIndent)
return this
}

Expand Down

0 comments on commit 43664e2

Please sign in to comment.