Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[gfm] Implement single/double tildes for strikethrough #165

Merged
merged 1 commit into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ open class GFMFlavourDescriptor(
override fun createHtmlGeneratingProviders(linkMap: LinkMap,
baseURI: URI?): Map<IElementType, GeneratingProvider> {
return super.createHtmlGeneratingProviders(linkMap, baseURI) + hashMapOf(
GFMElementTypes.STRIKETHROUGH to object : SimpleInlineTagProvider("span", 2, -2) {
GFMElementTypes.STRIKETHROUGH to object : EqualDelimiterTrimmingInlineTagProvider("span", GFMTokenTypes.TILDE) {
override fun openTag(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
visitor.consumeTagOpen(node, tagName, "class=\"user-del\"")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import org.intellij.markdown.parser.sequentialparsers.SequentialParser
import org.intellij.markdown.parser.sequentialparsers.TokensCache
import org.intellij.markdown.parser.sequentialparsers.impl.EmphStrongDelimiterParser

class StrikeThroughDelimiterParser: DelimiterParser() {
class StrikeThroughDelimiterParser : DelimiterParser() {
override fun scan(tokens: TokensCache, iterator: TokensCache.Iterator, delimiters: MutableList<Info>): Int {
if (iterator.type != GFMTokenTypes.TILDE) {
return 0
Expand Down Expand Up @@ -40,25 +40,39 @@ class StrikeThroughDelimiterParser: DelimiterParser() {
delimiters: MutableList<Info>,
result: SequentialParser.ParsingResultBuilder
) {
var shouldSkipNext = false
for (index in delimiters.indices.reversed()) {
if (shouldSkipNext) {
shouldSkipNext = false
// Start at the end and move backward, matching tokens
var index = delimiters.size - 1

while (index > 0) {
// Find opening tilde
if (!delimiters[index].isOpeningTilde()) {
index -= 1
continue
}
val opener = delimiters[index]
if (opener.tokenType != GFMTokenTypes.TILDE || opener.closerIndex == -1) {
continue
var openerIndex = index
var closerIndex = delimiters[index].closerIndex

// Attempt to widen the matched delimiters
var delimitersMatched = 1
while (EmphStrongDelimiterParser.areAdjacentSameMarkers(delimiters, openerIndex, closerIndex)) {
openerIndex -= 1
closerIndex += 1
delimitersMatched += 1
}
shouldSkipNext = EmphStrongDelimiterParser.areAdjacentSameMarkers(delimiters, index, opener.closerIndex)
val closer = delimiters[opener.closerIndex]
if (shouldSkipNext) {
val node = SequentialParser.Node(
range = opener.position - 1..closer.position + 2,
type = GFMElementTypes.STRIKETHROUGH
)
result.withNode(node)

// If 3 or more delimiters are matched, ignore
if (delimitersMatched < 3) {
val opener = delimiters[openerIndex]
val closer = delimiters[closerIndex]

result.withNode(SequentialParser.Node(opener.position..closer.position + 1, GFMElementTypes.STRIKETHROUGH))
}

// Update index
index = openerIndex - 1
}
}
}

private fun DelimiterParser.Info.isOpeningTilde(): Boolean =
tokenType == GFMTokenTypes.TILDE && closerIndex != -1
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.intellij.markdown.html

import org.intellij.markdown.IElementType
import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.MarkdownTokenTypes
import org.intellij.markdown.ast.*
Expand All @@ -8,7 +9,6 @@ import org.intellij.markdown.ast.impl.ListItemCompositeNode
import org.intellij.markdown.html.entities.EntityConverter
import org.intellij.markdown.lexer.Compat.assert
import org.intellij.markdown.parser.LinkMap
import kotlin.text.Regex

abstract class OpenCloseGeneratingProvider : GeneratingProvider {
abstract fun openTag(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode)
Expand Down Expand Up @@ -53,7 +53,7 @@ open class SimpleTagProvider(val tagName: String) : OpenCloseGeneratingProvider(
}

open class SimpleInlineTagProvider(val tagName: String, val renderFrom: Int = 0, val renderTo: Int = 0)
: InlineHolderGeneratingProvider() {
: InlineHolderGeneratingProvider() {
override fun openTag(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
visitor.consumeTagOpen(node, tagName)
}
Expand All @@ -67,6 +67,40 @@ open class SimpleInlineTagProvider(val tagName: String, val renderFrom: Int = 0,
}
}

/**
* Trims an equal number of delimiters from the start and end off the children.
* Meant to be used to trim surrounding tildes ('~') for strikethroughs.
*/
open class EqualDelimiterTrimmingInlineTagProvider(val tagName: String, val delimiterType: IElementType)
: InlineHolderGeneratingProvider() {
override fun openTag(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
visitor.consumeTagOpen(node, tagName)
}

override fun closeTag(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
visitor.consumeTagClose(tagName)
}

override fun childrenToRender(node: ASTNode): List<ASTNode> {
if (node.children.isEmpty()) return node.children

var left = 0
var right = node.children.size - 1

while (
node.children[left].type == delimiterType &&
node.children[right].type == delimiterType
) {
left += 1
right -= 1

if (left >= right) break
}

return node.children.subList(left, right + 1)
}
}

class CodeSpanGeneratingProvider: GeneratingProvider {
override fun processNode(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
val nodes = node.children.subList(1, node.children.size - 1)
Expand Down
27 changes: 22 additions & 5 deletions src/commonTest/kotlin/org/intellij/markdown/GfmSpecTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3001,16 +3001,34 @@ class GfmSpecTest : SpecTest(org.intellij.markdown.flavours.gfm.GFMFlavourDescri
// html = "<p><del>Hi</del> Hello, world!</p>\n"
//)

//@Test
//fun testStrikethroughExample491() = doTest(
// markdown = "~~Hi~~ Hello, world!\n",
// html = "<p><span class=\"user-del\">Hi</span> Hello, world!</p>\n"
//)

@Test
fun testStrikethroughExample491() = doTest(
markdown = "~~Hi~~ Hello, world!\n",
html = "<p><span class=\"user-del\">Hi</span> Hello, world!</p>\n"
markdown = "~~Hi~~ Hello, ~there~ world!\n",
html = "<p><span class=\"user-del\">Hi</span> Hello, <span class=\"user-del\">there</span> world!</p>\n"
)

@Test
fun testStrikethroughExample492() = doTest(
markdown = "This ~~has a\n\nnew paragraph~~.\n",
html = "<p>This ~~has a</p>\n<p>new paragraph~~.</p>\n"
markdown = "This ~~has a\n\nnew paragraph~~.\n",
html = "<p>This ~~has a</p>\n<p>new paragraph~~.</p>\n"
)

@Test
fun testStrikethroughExample493() = doTest(
markdown = "This will ~~~not~~~ strike.\n",
html = "<p>This will ~~~not~~~ strike.</p>\n"
)

@Test
fun testStrikethroughManyWords() = doTest(
markdown = "~~Two (so many) words~~\n",
html = "<p><span class=\"user-del\">Two (so many) words</span></p>\n"
)

@Test
Expand Down Expand Up @@ -4151,5 +4169,4 @@ class GfmSpecTest : SpecTest(org.intellij.markdown.flavours.gfm.GFMFlavourDescri
markdown = "Multiple spaces\n",
html = "<p>Multiple spaces</p>\n"
)

}
4 changes: 2 additions & 2 deletions src/fileBasedTest/resources/data/html/ruby17351.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,9 +379,9 @@ Zookeeper is required for SolrCloud to maintain and synchronize the Solr server
1. `./zkcli.sh -cmd clear -z localhost:2181/kla_chroot /kla_chroot`


#### *~BETA~*
#### *\~BETA\~*
### Jena/Fuseki Local Persistence
#### *~BETA~*
#### *\~BETA\~*
The Knowtify Data Service requires a local data store to be configured so that local data can be stored, managed, and served up to clients. There are 2 possible configurations of the lcoal data store, both of which involve the jena TDB database.

1. Embedded (the database runs in-process to the Data Service). Use this in environments where you are certain there will only ever be a single instance of the Knowtify Data Service to server all of the users.
Expand Down
Loading