fixed bug with reformatting code sequences in markdown + lyng
This commit is contained in:
parent
9e138367ef
commit
aad7c9619b
1
.gitignore
vendored
1
.gitignore
vendored
@ -20,3 +20,4 @@ xcuserdata
|
|||||||
.output*.txt
|
.output*.txt
|
||||||
debug.log
|
debug.log
|
||||||
/build.log
|
/build.log
|
||||||
|
/test.md
|
||||||
|
|||||||
@ -100,6 +100,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
- `lyng --help` shows `fmt`; `lyng fmt --help` displays dedicated help.
|
- `lyng --help` shows `fmt`; `lyng fmt --help` displays dedicated help.
|
||||||
- Fix: Property accessors (`get`, `set`, `private set`, `protected set`) are now correctly indented relative to the property declaration.
|
- Fix: Property accessors (`get`, `set`, `private set`, `protected set`) are now correctly indented relative to the property declaration.
|
||||||
- Fix: Indentation now correctly carries over into blocks that start on extra‑indented lines (e.g., nested `if` statements or property accessor bodies).
|
- Fix: Indentation now correctly carries over into blocks that start on extra‑indented lines (e.g., nested `if` statements or property accessor bodies).
|
||||||
|
- Fix: Formatting Markdown files no longer deletes content in `.lyng` code fences and works correctly with injected files (resolves clobbering, `StringIndexOutOfBoundsException`, and `nonempty text is not covered by block` errors).
|
||||||
|
|
||||||
- CLI: Preserved legacy script invocation fast-paths:
|
- CLI: Preserved legacy script invocation fast-paths:
|
||||||
- `lyng script.lyng [args...]` executes the script directly.
|
- `lyng script.lyng [args...]` executes the script directly.
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -42,7 +42,7 @@ private class LineBlocksRootBlock(
|
|||||||
private val file: PsiFile,
|
private val file: PsiFile,
|
||||||
private val settings: CodeStyleSettings
|
private val settings: CodeStyleSettings
|
||||||
) : Block {
|
) : Block {
|
||||||
override fun getTextRange(): TextRange = file.textRange
|
override fun getTextRange(): TextRange = TextRange(0, file.textLength)
|
||||||
|
|
||||||
override fun getSubBlocks(): List<Block> = emptyList()
|
override fun getSubBlocks(): List<Block> = emptyList()
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ private class LineBlocksRootBlock(
|
|||||||
override fun getSpacing(child1: Block?, child2: Block): Spacing? = null
|
override fun getSpacing(child1: Block?, child2: Block): Spacing? = null
|
||||||
override fun getChildAttributes(newChildIndex: Int): ChildAttributes = ChildAttributes(Indent.getNoneIndent(), null)
|
override fun getChildAttributes(newChildIndex: Int): ChildAttributes = ChildAttributes(Indent.getNoneIndent(), null)
|
||||||
override fun isIncomplete(): Boolean = false
|
override fun isIncomplete(): Boolean = false
|
||||||
override fun isLeaf(): Boolean = false
|
override fun isLeaf(): Boolean = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intentionally no sub-blocks/spacing: indentation is handled by PreFormatProcessor + LineIndentProvider
|
// Intentionally no sub-blocks/spacing: indentation is handled by PreFormatProcessor + LineIndentProvider
|
||||||
|
|||||||
@ -44,25 +44,67 @@ class LyngPreFormatProcessor : PreFormatProcessor {
|
|||||||
// When both spacing and wrapping are OFF, still fix indentation for the whole file to
|
// When both spacing and wrapping are OFF, still fix indentation for the whole file to
|
||||||
// guarantee visible changes on Reformat Code.
|
// guarantee visible changes on Reformat Code.
|
||||||
val runFullFileIndent = !settings.enableSpacing && !settings.enableWrapping
|
val runFullFileIndent = !settings.enableSpacing && !settings.enableWrapping
|
||||||
// Maintain a working range and a modification flag to avoid stale offsets after replacements
|
|
||||||
var modified = false
|
var modified = false
|
||||||
fun fullRange(): TextRange = TextRange(0, doc.textLength)
|
|
||||||
var workingRange: TextRange = range.intersection(fullRange()) ?: fullRange()
|
|
||||||
|
|
||||||
val startLine = if (runFullFileIndent) 0 else doc.getLineNumber(workingRange.startOffset)
|
val docW = doc as? com.intellij.injected.editor.DocumentWindow
|
||||||
val endLine = if (runFullFileIndent) (doc.lineCount - 1).coerceAtLeast(0)
|
// The host range of the entire injected fragment (or the whole file if not injected).
|
||||||
else doc.getLineNumber(workingRange.endOffset.coerceAtMost(doc.textLength))
|
fun currentHostRange(): TextRange = if (docW != null) {
|
||||||
|
TextRange(docW.injectedToHost(0), docW.injectedToHost(doc.textLength))
|
||||||
|
} else {
|
||||||
|
file.textRange
|
||||||
|
}
|
||||||
|
|
||||||
|
// The range in 'doc' coordinate system (local 0..len for injections, host offsets for normal files).
|
||||||
|
fun currentLocalRange(): TextRange = if (docW != null) {
|
||||||
|
TextRange(0, doc.textLength)
|
||||||
|
} else {
|
||||||
|
file.textRange
|
||||||
|
}
|
||||||
|
|
||||||
|
val clr = currentLocalRange()
|
||||||
|
val chr = currentHostRange()
|
||||||
|
|
||||||
|
// Convert the input range to the coordinate system of 'doc'
|
||||||
|
var workingRangeLocal: TextRange = if (docW != null) {
|
||||||
|
val hostIntersection = range.intersection(chr)
|
||||||
|
if (hostIntersection != null) {
|
||||||
|
try {
|
||||||
|
val start = docW.hostToInjected(hostIntersection.startOffset)
|
||||||
|
val end = docW.hostToInjected(hostIntersection.endOffset)
|
||||||
|
TextRange(start.coerceAtMost(end), end.coerceAtLeast(start))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
clr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
range.intersection(clr) ?: clr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
range.intersection(clr) ?: clr
|
||||||
|
}
|
||||||
|
|
||||||
|
val startLine = if (runFullFileIndent) {
|
||||||
|
doc.getLineNumber(currentLocalRange().startOffset)
|
||||||
|
} else {
|
||||||
|
doc.getLineNumber(workingRangeLocal.startOffset)
|
||||||
|
}
|
||||||
|
val endLine = if (runFullFileIndent) {
|
||||||
|
if (clr.endOffset <= clr.startOffset) doc.getLineNumber(clr.startOffset)
|
||||||
|
else doc.getLineNumber(clr.endOffset)
|
||||||
|
} else {
|
||||||
|
doc.getLineNumber(workingRangeLocal.endOffset.coerceAtMost(doc.textLength))
|
||||||
|
}
|
||||||
|
|
||||||
fun codePart(s: String): String {
|
fun codePart(s: String): String {
|
||||||
val idx = s.indexOf("//")
|
val idx = s.indexOf("//")
|
||||||
return if (idx >= 0) s.substring(0, idx) else s
|
return if (idx >= 0) s.substring(0, idx) else s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-scan to compute balances up to startLine
|
// Pre-scan to compute balances up to startLine.
|
||||||
|
val fragmentStartLine = doc.getLineNumber(currentLocalRange().startOffset)
|
||||||
var blockLevel = 0
|
var blockLevel = 0
|
||||||
var parenBalance = 0
|
var parenBalance = 0
|
||||||
var bracketBalance = 0
|
var bracketBalance = 0
|
||||||
for (ln in 0 until startLine) {
|
for (ln in fragmentStartLine until startLine) {
|
||||||
val text = doc.getText(TextRange(doc.getLineStartOffset(ln), doc.getLineEndOffset(ln)))
|
val text = doc.getText(TextRange(doc.getLineStartOffset(ln), doc.getLineEndOffset(ln)))
|
||||||
for (ch in codePart(text)) when (ch) {
|
for (ch in codePart(text)) when (ch) {
|
||||||
'{' -> blockLevel++
|
'{' -> blockLevel++
|
||||||
@ -85,7 +127,6 @@ class LyngPreFormatProcessor : PreFormatProcessor {
|
|||||||
CodeStyleManager.getInstance(project).adjustLineIndent(file, lineStart)
|
CodeStyleManager.getInstance(project).adjustLineIndent(file, lineStart)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Log as debug because this can be called many times during reformat
|
// Log as debug because this can be called many times during reformat
|
||||||
// and we don't want to spam warnings if it's a known platform issue with injections
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,15 +151,14 @@ class LyngPreFormatProcessor : PreFormatProcessor {
|
|||||||
useTabs = options.USE_TAB_CHARACTER,
|
useTabs = options.USE_TAB_CHARACTER,
|
||||||
continuationIndentSize = options.CONTINUATION_INDENT_SIZE.coerceAtLeast(options.INDENT_SIZE.coerceAtLeast(1)),
|
continuationIndentSize = options.CONTINUATION_INDENT_SIZE.coerceAtLeast(options.INDENT_SIZE.coerceAtLeast(1)),
|
||||||
)
|
)
|
||||||
val full = fullRange()
|
val r = if (runFullFileIndent) currentLocalRange() else workingRangeLocal.intersection(currentLocalRange()) ?: currentLocalRange()
|
||||||
val r = if (runFullFileIndent) full else workingRange.intersection(full) ?: full
|
|
||||||
val text = doc.getText(r)
|
val text = doc.getText(r)
|
||||||
val formatted = LyngFormatter.reindent(text, cfg)
|
val formatted = LyngFormatter.reindent(text, cfg)
|
||||||
if (formatted != text) {
|
if (formatted != text) {
|
||||||
doc.replaceString(r.startOffset, r.endOffset, formatted)
|
doc.replaceString(r.startOffset, r.endOffset, formatted)
|
||||||
modified = true
|
modified = true
|
||||||
psiDoc.commitDocument(doc)
|
psiDoc.commitDocument(doc)
|
||||||
workingRange = fullRange()
|
workingRangeLocal = currentLocalRange()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,14 +171,14 @@ class LyngPreFormatProcessor : PreFormatProcessor {
|
|||||||
applySpacing = true,
|
applySpacing = true,
|
||||||
applyWrapping = false,
|
applyWrapping = false,
|
||||||
)
|
)
|
||||||
val safe = workingRange.intersection(fullRange()) ?: fullRange()
|
val r = if (runFullFileIndent) currentLocalRange() else workingRangeLocal.intersection(currentLocalRange()) ?: currentLocalRange()
|
||||||
val text = doc.getText(safe)
|
val text = doc.getText(r)
|
||||||
val formatted = LyngFormatter.format(text, cfg)
|
val formatted = LyngFormatter.format(text, cfg)
|
||||||
if (formatted != text) {
|
if (formatted != text) {
|
||||||
doc.replaceString(safe.startOffset, safe.endOffset, formatted)
|
doc.replaceString(r.startOffset, r.endOffset, formatted)
|
||||||
modified = true
|
modified = true
|
||||||
psiDoc.commitDocument(doc)
|
psiDoc.commitDocument(doc)
|
||||||
workingRange = fullRange()
|
workingRangeLocal = currentLocalRange()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Optionally apply wrapping (after spacing) when enabled
|
// Optionally apply wrapping (after spacing) when enabled
|
||||||
@ -150,17 +190,19 @@ class LyngPreFormatProcessor : PreFormatProcessor {
|
|||||||
applySpacing = settings.enableSpacing,
|
applySpacing = settings.enableSpacing,
|
||||||
applyWrapping = true,
|
applyWrapping = true,
|
||||||
)
|
)
|
||||||
val safe2 = workingRange.intersection(fullRange()) ?: fullRange()
|
val r = if (runFullFileIndent) currentLocalRange() else workingRangeLocal.intersection(currentLocalRange()) ?: currentLocalRange()
|
||||||
val text2 = doc.getText(safe2)
|
val text = doc.getText(r)
|
||||||
val wrapped = LyngFormatter.format(text2, cfg)
|
val wrapped = LyngFormatter.format(text, cfg)
|
||||||
if (wrapped != text2) {
|
if (wrapped != text) {
|
||||||
doc.replaceString(safe2.startOffset, safe2.endOffset, wrapped)
|
doc.replaceString(r.startOffset, r.endOffset, wrapped)
|
||||||
modified = true
|
modified = true
|
||||||
psiDoc.commitDocument(doc)
|
psiDoc.commitDocument(doc)
|
||||||
workingRange = fullRange()
|
workingRangeLocal = currentLocalRange()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Return a safe range for the formatter to continue with, preventing stale offsets
|
// Return a safe range for the formatter to continue with, preventing stale offsets.
|
||||||
return if (modified) fullRange() else (range.intersection(fullRange()) ?: fullRange())
|
// For injected files, ALWAYS return a range in local coordinates.
|
||||||
|
val finalRange = currentLocalRange()
|
||||||
|
return if (modified) finalRange else (range.intersection(finalRange) ?: finalRange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
~ Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
~ Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
~
|
~
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
~ you may not use this file except in compliance with the License.
|
~ you may not use this file except in compliance with the License.
|
||||||
@ -329,7 +329,7 @@
|
|||||||
<!-- Top-left version ribbon -->
|
<!-- Top-left version ribbon -->
|
||||||
<div class="corner-ribbon bg-danger text-white">
|
<div class="corner-ribbon bg-danger text-white">
|
||||||
<span style="margin-left: -5em">
|
<span style="margin-left: -5em">
|
||||||
v1.1.0-beta2
|
v1.1.0-rc
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- Fixed top navbar for the whole site -->
|
<!-- Fixed top navbar for the whole site -->
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -60,7 +60,7 @@ class HighlightSmokeTest {
|
|||||||
// Spread operator present
|
// Spread operator present
|
||||||
assertContains(html, "<span class=\"hl-op\">...</span>")
|
assertContains(html, "<span class=\"hl-op\">...</span>")
|
||||||
// String key and identifier key appear
|
// String key and identifier key appear
|
||||||
assertContains(html, "<span class=\"hl-str\">\"a\"</span>")
|
assertContains(html, "<span class=\"hl-str\">"a"</span>")
|
||||||
assertContains(html, "<span class=\"hl-id\">b</span>")
|
assertContains(html, "<span class=\"hl-id\">b</span>")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user