diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e244a06a..20e598c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ on: - master env: - GO_VERSION: "~1.20.5" + GO_VERSION: "~1.21.6" jobs: # Runs Golangci-lint on the source code diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index d3db7abf..7773ec26 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -16,7 +16,7 @@ jobs: - name: Set Up Go uses: actions/setup-go@v5 with: - go-version: "1.20" + go-version: "1.21" - name: Run GoReleaser uses: goreleaser/goreleaser-action@v5 with: diff --git a/go.mod b/go.mod index 089b557f..e976130f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/kovetskiy/mark -go 1.19 +go 1.21 + +toolchain go1.21.6 require ( github.com/bmatcuk/doublestar/v4 v4.6.1 @@ -12,7 +14,7 @@ require ( github.com/reconquest/regexputil-go v0.0.0-20160905154124-38573e70c1f4 github.com/stretchr/testify v1.8.4 github.com/urfave/cli/v2 v2.27.1 - github.com/yuin/goldmark v1.5.6 + github.com/yuin/goldmark v1.6.0 golang.org/x/tools v0.17.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 19092f47..5beb7037 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,8 @@ github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= -github.com/yuin/goldmark v1.5.6 h1:COmQAWTCcGetChm3Ig7G/t8AFAN00t+o8Mt4cf7JpwA= -github.com/yuin/goldmark v1.5.6/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= +github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zazab/zhash v0.0.0-20210630080733-6e809466f8d3 h1:BhVaeQJc3xalHGONn215FylzuxdQBIT3d/aRjDg4nXQ= github.com/zazab/zhash v0.0.0-20210630080733-6e809466f8d3/go.mod h1:NtepZ8TEXErPsmQDMUoN72f8aIy4+xNinSJ3f1giess= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -72,5 +72,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/mark/renderer/text.go b/pkg/mark/renderer/text.go index 56f752e6..ef7865f0 100644 --- a/pkg/mark/renderer/text.go +++ b/pkg/mark/renderer/text.go @@ -1,6 +1,7 @@ package renderer import ( + "unicode" "unicode/utf8" "github.com/yuin/goldmark/ast" @@ -39,7 +40,7 @@ func (r *ConfluenceTextRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegi reg.Register(ast.KindText, r.renderText) } -// This is taken from https://github.com/yuin/goldmark/blob/v1.5.6/renderer/html/html.go#L648 +// This is taken from https://github.com/yuin/goldmark/blob/v1.6.0/renderer/html/html.go#L719 // with the hardcoded '\n' for soft breaks swapped for the configurable r.softBreak func (r *ConfluenceTextRenderer) renderText(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { @@ -59,14 +60,24 @@ func (r *ConfluenceTextRenderer) renderText(w util.BufWriter, source []byte, nod _, _ = w.WriteString("
\n") } } else if n.SoftLineBreak() { - if r.EastAsianLineBreaks && len(value) != 0 { + if r.EastAsianLineBreaks != html.EastAsianLineBreaksNone && len(value) != 0 { sibling := node.NextSibling() if sibling != nil && sibling.Kind() == ast.KindText { if siblingText := sibling.(*ast.Text).Text(source); len(siblingText) != 0 { thisLastRune := util.ToRune(value, len(value)-1) siblingFirstRune, _ := utf8.DecodeRune(siblingText) - if !(util.IsEastAsianWideRune(thisLastRune) && - util.IsEastAsianWideRune(siblingFirstRune)) { + // Inline the softLineBreak function as it's not public + writeLineBreak := false + switch r.EastAsianLineBreaks { + case html.EastAsianLineBreaksNone: + writeLineBreak = false + case html.EastAsianLineBreaksSimple: + writeLineBreak = !(util.IsEastAsianWideRune(thisLastRune) && util.IsEastAsianWideRune(siblingFirstRune)) + case html.EastAsianLineBreaksCSS3Draft: + writeLineBreak = eastAsianLineBreaksCSS3DraftSoftLineBreak(thisLastRune, siblingFirstRune) + } + + if writeLineBreak { _ = w.WriteByte(byte(r.softBreak)) } } @@ -78,3 +89,48 @@ func (r *ConfluenceTextRenderer) renderText(w util.BufWriter, source []byte, nod } return ast.WalkContinue, nil } + +func eastAsianLineBreaksCSS3DraftSoftLineBreak(thisLastRune rune, siblingFirstRune rune) bool { + // Implements CSS text level3 Segment Break Transformation Rules with some enhancements. + // References: + // - https://www.w3.org/TR/2020/WD-css-text-3-20200429/#line-break-transform + // - https://github.com/w3c/csswg-drafts/issues/5086 + + // Rule1: + // If the character immediately before or immediately after the segment break is + // the zero-width space character (U+200B), then the break is removed, leaving behind the zero-width space. + if thisLastRune == '\u200B' || siblingFirstRune == '\u200B' { + return false + } + + // Rule2: + // Otherwise, if the East Asian Width property of both the character before and after the segment break is + // F, W, or H (not A), and neither side is Hangul, then the segment break is removed. + thisLastRuneEastAsianWidth := util.EastAsianWidth(thisLastRune) + siblingFirstRuneEastAsianWidth := util.EastAsianWidth(siblingFirstRune) + if (thisLastRuneEastAsianWidth == "F" || + thisLastRuneEastAsianWidth == "W" || + thisLastRuneEastAsianWidth == "H") && + (siblingFirstRuneEastAsianWidth == "F" || + siblingFirstRuneEastAsianWidth == "W" || + siblingFirstRuneEastAsianWidth == "H") { + return unicode.Is(unicode.Hangul, thisLastRune) || unicode.Is(unicode.Hangul, siblingFirstRune) + } + + // Rule3: + // Otherwise, if either the character before or after the segment break belongs to + // the space-discarding character set and it is a Unicode Punctuation (P*) or U+3000, + // then the segment break is removed. + if util.IsSpaceDiscardingUnicodeRune(thisLastRune) || + unicode.IsPunct(thisLastRune) || + thisLastRune == '\u3000' || + util.IsSpaceDiscardingUnicodeRune(siblingFirstRune) || + unicode.IsPunct(siblingFirstRune) || + siblingFirstRune == '\u3000' { + return false + } + + // Rule4: + // Otherwise, the segment break is converted to a space (U+0020). + return true +}