Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package fx for openSUSE:Factory checked in at 2026-10-26 10:00:14 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/fx (Old) and /work/SRC/openSUSE:Factory/.fx.new.2252680 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "fx" Mon Oct 26 10:00:14 2026 rev:12 rq:1392376 version:40.0.1 Changes: -------- --- /work/SRC/openSUSE:Factory/fx/fx.changes 2026-10-06 19:18:08.029825512 +0200 +++ /work/SRC/openSUSE:Factory/.fx.new.2252680/fx.changes 2026-10-26 10:01:02.848342985 +0100 @@ -1,0 +2,7 @@ +Sun Oct 25 08:36:49 UTC 2026 - Martin Hauke <[email protected]> + +- Update to version 40.0.1 + * Fixed rendering of Unicode characters in go-to-symbol matches + (#440). + +------------------------------------------------------------------- Old: ---- fx-40.0.0.tar.gz New: ---- fx-40.0.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ fx.spec ++++++ --- /var/tmp/diff_new_pack.rD4NSx/_old 2026-10-26 10:01:04.028392494 +0100 +++ /var/tmp/diff_new_pack.rD4NSx/_new 2026-10-26 10:01:04.030392578 +0100 @@ -18,7 +18,7 @@ Name: fx -Version: 40.0.0 +Version: 40.0.1 Release: 0 Summary: Terminal JSON viewer License: MIT ++++++ fx-40.0.0.tar.gz -> fx-40.0.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fx-40.0.0/.github/workflows/docker.yml new/fx-40.0.1/.github/workflows/docker.yml --- old/fx-40.0.0/.github/workflows/docker.yml 2026-10-05 17:50:42.000000000 +0200 +++ new/fx-40.0.1/.github/workflows/docker.yml 2026-10-22 15:49:22.000000000 +0200 @@ -7,19 +7,19 @@ runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: antonmedv password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Build and push - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v7 with: context: . file: ./Dockerfile diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fx-40.0.0/.github/workflows/snap.yml new/fx-40.0.1/.github/workflows/snap.yml --- old/fx-40.0.0/.github/workflows/snap.yml 2026-10-05 17:50:42.000000000 +0200 +++ new/fx-40.0.1/.github/workflows/snap.yml 2026-10-22 15:49:22.000000000 +0200 @@ -6,7 +6,7 @@ snap: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: ref: master diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fx-40.0.0/.github/workflows/test.yml new/fx-40.0.1/.github/workflows/test.yml --- old/fx-40.0.0/.github/workflows/test.yml 2026-10-05 17:50:42.000000000 +0200 +++ new/fx-40.0.1/.github/workflows/test.yml 2026-10-22 15:49:22.000000000 +0200 @@ -10,26 +10,26 @@ go: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 with: - go-version: 1.21 + go-version-file: go.mod - name: Test run: go test ./... go-arm: runs-on: ubuntu-24.04-arm steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 with: - go-version: 1.21 + go-version-file: go.mod - name: Test on ARM run: go test ./... node: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 - name: Test NPM version run: cd npm && node test.js diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fx-40.0.0/go.mod new/fx-40.0.1/go.mod --- old/fx-40.0.0/go.mod 2026-10-05 17:50:42.000000000 +0200 +++ new/fx-40.0.1/go.mod 2026-10-22 15:49:22.000000000 +0200 @@ -20,7 +20,7 @@ github.com/pelletier/go-toml/v2 v2.4.3 github.com/rivo/uniseg v0.4.7 github.com/stretchr/testify v1.12.1 - golang.org/x/net v0.52.0 + golang.org/x/net v0.55.0 golang.org/x/text v0.42.0 ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fx-40.0.0/go.sum new/fx-40.0.1/go.sum --- old/fx-40.0.0/go.sum 2026-10-05 17:50:42.000000000 +0200 +++ new/fx-40.0.1/go.sum 2026-10-22 15:49:22.000000000 +0200 @@ -64,8 +64,8 @@ github.com/xo/terminfo v1.2.0/go.mod h1:lGzkSo8Fe7IRh/w+Gqz7n5mDog4FVXCj3gy/DYTBqio= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fx-40.0.0/goto_symbol_test.go new/fx-40.0.1/goto_symbol_test.go --- old/fx-40.0.0/goto_symbol_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/fx-40.0.1/goto_symbol_test.go 2026-10-22 15:49:22.000000000 +0200 @@ -0,0 +1,58 @@ +package main + +import ( + "strings" + "testing" + "unicode/utf8" + + "github.com/charmbracelet/x/ansi" + "github.com/mattn/go-runewidth" + "github.com/stretchr/testify/require" + + "github.com/antonmedv/fx/internal/fuzzy" + "github.com/antonmedv/fx/internal/theme" +) + +func TestGotoSymbolUnicodeRendering(t *testing.T) { + saved := theme.CurrentTheme + t.Cleanup(func() { theme.CurrentTheme = saved }) + theme.CurrentTheme.StatusBar = func(s string) string { return s } + theme.CurrentTheme.Search = func(s string) string { return "\x1b[31m" + s + "\x1b[0m" } + + for _, tc := range []struct { + text, pattern string + }{ + {"name", "m"}, + {"名前", "前"}, + {"éclair", "c"}, + {"🍎apple", "a"}, + } { + t.Run(tc.text, func(t *testing.T) { + m := newQueryModel(t, `{"name": 1}`) + m.gotoSymbolInput = newInput() + m.gotoSymbolInput.Focus() + m.fuzzyMatch = fuzzy.Find([]rune(tc.pattern), []string{tc.text}) + require.NotNil(t, m.fuzzyMatch) + + lines := strings.Split(view(m), "\n") + status := lines[len(lines)-2] + require.True(t, utf8.ValidString(status)) + require.Equal(t, tc.text, strings.TrimRight(ansi.Strip(status), " ")) + require.Contains(t, status, "\x1b[31m"+tc.pattern+"\x1b[0m") + require.Equal(t, m.termWidth, runewidth.StringWidth(ansi.Strip(status))) + }) + } +} + +func TestGotoSymbolUnicodeKey(t *testing.T) { + m := newQueryModel(t, `{"名前": 1}`) + m.gotoSymbolInput = newInput() + pressKey(m, press("@")) + typeKeys(m, "前") + require.NotNil(t, m.fuzzyMatch) + + lines := strings.Split(view(m), "\n") + status := ansi.Strip(lines[len(lines)-2]) + require.Contains(t, status, "名前") + require.Equal(t, m.termWidth, runewidth.StringWidth(status)) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fx-40.0.0/npm/package.json new/fx-40.0.1/npm/package.json --- old/fx-40.0.0/npm/package.json 2026-10-05 17:50:42.000000000 +0200 +++ new/fx-40.0.1/npm/package.json 2026-10-22 15:49:22.000000000 +0200 @@ -1,6 +1,6 @@ { "name": "fx", - "version": "40.0.0", + "version": "40.0.1", "bin": { "fx": "index.js" }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fx-40.0.0/snap/snapcraft.yaml new/fx-40.0.1/snap/snapcraft.yaml --- old/fx-40.0.0/snap/snapcraft.yaml 2026-10-05 17:50:42.000000000 +0200 +++ new/fx-40.0.1/snap/snapcraft.yaml 2026-10-22 15:49:22.000000000 +0200 @@ -18,7 +18,7 @@ Other install methods (Homebrew, GitHub release binaries, go install) have no such restriction: https://fx.wtf/install -version: 40.0.0 +version: 40.0.1 base: core24 grade: stable confinement: strict diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fx-40.0.0/version.go new/fx-40.0.1/version.go --- old/fx-40.0.0/version.go 2026-10-05 17:50:42.000000000 +0200 +++ new/fx-40.0.1/version.go 2026-10-22 15:49:22.000000000 +0200 @@ -1,3 +1,3 @@ package main -const version = "40.0.0" +const version = "40.0.1" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fx-40.0.0/view.go new/fx-40.0.1/view.go --- old/fx-40.0.0/view.go 2026-10-05 17:50:42.000000000 +0200 +++ new/fx-40.0.1/view.go 2026-10-22 15:49:22.000000000 +0200 @@ -7,6 +7,7 @@ "strings" tea "charm.land/bubbletea/v2" + "github.com/mattn/go-runewidth" "github.com/antonmedv/fx/internal/ident" . "github.com/antonmedv/fx/internal/jsonx" @@ -163,14 +164,14 @@ if m.gotoSymbolInput.Focused() && m.fuzzyMatch != nil { var matchedStr []byte str := m.fuzzyMatch.Str - for i := 0; i < len(str); i++ { + for i, char := range []rune(str) { if utils.Contains(i, m.fuzzyMatch.Pos) { - matchedStr = append(matchedStr, theme.CurrentTheme.Search(string(str[i]))...) + matchedStr = append(matchedStr, theme.CurrentTheme.Search(string(char))...) } else { - matchedStr = append(matchedStr, theme.CurrentTheme.StatusBar(string(str[i]))...) + matchedStr = append(matchedStr, theme.CurrentTheme.StatusBar(string(char))...) } } - repeatCount := m.termWidth - len(str) + repeatCount := m.termWidth - runewidth.StringWidth(str) if repeatCount > 0 { matchedStr = append(matchedStr, theme.CurrentTheme.StatusBar(strings.Repeat(" ", repeatCount))...) } ++++++ vendor.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/net/html/parse.go new/vendor/golang.org/x/net/html/parse.go --- old/vendor/golang.org/x/net/html/parse.go 2026-10-05 17:50:42.000000000 +0200 +++ new/vendor/golang.org/x/net/html/parse.go 2026-10-22 15:49:22.000000000 +0200 @@ -5,9 +5,11 @@ package html import ( + "cmp" "errors" "fmt" "io" + "slices" "strings" a "golang.org/x/net/html/atom" @@ -328,6 +330,14 @@ }) } +func attrCompare(a, b Attribute) int { + return cmp.Or( + cmp.Compare(a.Namespace, b.Namespace), + cmp.Compare(a.Key, b.Key), + cmp.Compare(a.Val, b.Val), + ) +} + // addElement adds a child element based on the current token. func (p *parser) addElement() { p.addChild(&Node{ @@ -343,6 +353,10 @@ tagAtom, attr := p.tok.DataAtom, p.tok.Attr p.addElement() + // In order to optimize the search, we need the attributes to be sorted, so we + // can just use slices.Equal. + slices.SortFunc(attr, attrCompare) + // Implement the Noah's Ark clause, but with three per family instead of two. identicalElements := 0 findIdenticalElements: @@ -360,19 +374,7 @@ if n.DataAtom != tagAtom { continue } - if len(n.Attr) != len(attr) { - continue - } - compareAttributes: - for _, t0 := range n.Attr { - for _, t1 := range attr { - if t0.Key == t1.Key && t0.Namespace == t1.Namespace && t0.Val == t1.Val { - // Found a match for this attribute, continue with the next attribute. - continue compareAttributes - } - } - // If we get here, there is no attribute that matches a. - // Therefore the element is not identical to the new one. + if !slices.Equal(n.Attr, attr) { continue findIdenticalElements } @@ -382,7 +384,11 @@ } } - p.afe = append(p.afe, p.top()) + // Sort the attributes to optimize future identical-element searches. + top := p.top() + slices.SortFunc(top.Attr, attrCompare) + + p.afe = append(p.afe, top) } // Section 12.2.4.3. @@ -1372,8 +1378,6 @@ } // inBodyEndTagOther performs the "any other end tag" algorithm for inBodyIM. -// "Any other end tag" handling from 12.2.6.5 The rules for parsing tokens in foreign content -// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inforeign func (p *parser) inBodyEndTagOther(tagAtom a.Atom, tagName string) { for i := len(p.oe) - 1; i >= 0; i-- { // Two element nodes have the same tag if they have the same Data (a @@ -1383,7 +1387,7 @@ // Uncommon (custom) tags get a zero DataAtom. // // The if condition here is equivalent to (p.oe[i].Data == tagName). - if (p.oe[i].DataAtom == tagAtom) && + if p.oe[i].Namespace == "" && (p.oe[i].DataAtom == tagAtom) && ((tagAtom != 0) || (p.oe[i].Data == tagName)) { p.oe = p.oe[:i] break diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/net/html/render.go new/vendor/golang.org/x/net/html/render.go --- old/vendor/golang.org/x/net/html/render.go 2026-10-05 17:50:42.000000000 +0200 +++ new/vendor/golang.org/x/net/html/render.go 2026-10-22 15:49:22.000000000 +0200 @@ -113,14 +113,14 @@ if _, err := w.WriteString(" PUBLIC "); err != nil { return err } - if err := writeQuoted(w, p); err != nil { + if err := writeDoctypeQuoted(w, p); err != nil { return err } if s != "" { if err := w.WriteByte(' '); err != nil { return err } - if err := writeQuoted(w, s); err != nil { + if err := writeDoctypeQuoted(w, s); err != nil { return err } } @@ -128,7 +128,7 @@ if _, err := w.WriteString(" SYSTEM "); err != nil { return err } - if err := writeQuoted(w, s); err != nil { + if err := writeDoctypeQuoted(w, s); err != nil { return err } } @@ -243,27 +243,50 @@ if n.Namespace != "" { return false } + switch n.Data { case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp": + // We need to check if n is a node that was fostered from a HTML namespace + // into a non-HTML namespace (in which case, different rules apply to it). + // We do this by walking up the tree until we find a node with a non-empty + // namespace. If we find such a node, we also have to check if it's + // an HTML integration point. If it isn't, then the node we're currently + // looking at is foster-parented and we should return false. + for p := n.Parent; p != nil; p = p.Parent { + if p.Namespace != "" { + if !htmlIntegrationPoint(p) { + return false + } + break + } + } + return true default: return false } } -// writeQuoted writes s to w surrounded by quotes. Normally it will use double +// writeDoctypeQuoted writes s to w surrounded by quotes. Normally it will use double // quotes, but if s contains a double quote, it will use single quotes. +// If s contains any '>' characters, they are replaced with > in order +// to prevent triggering an abrupt-doctype-system-identifier parse error. // It is used for writing the identifiers in a doctype declaration. // In valid HTML, they can't contain both types of quotes. -func writeQuoted(w writer, s string) error { +func writeDoctypeQuoted(w writer, s string) error { var q byte = '"' if strings.Contains(s, `"`) { + // parseDoctype will never produce a Node with both quote types, but a user + // can construct their own Node that violates this assumption. + if strings.Contains(s, `'`) { + return errors.New("doctype contains both quote types, cannot be safely rendered") + } q = '\'' } if err := w.WriteByte(q); err != nil { return err } - if _, err := w.WriteString(s); err != nil { + if _, err := w.WriteString(strings.ReplaceAll(s, ">", ">")); err != nil { return err } if err := w.WriteByte(q); err != nil { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/net/html/token.go new/vendor/golang.org/x/net/html/token.go --- old/vendor/golang.org/x/net/html/token.go 2026-10-05 17:50:42.000000000 +0200 +++ new/vendor/golang.org/x/net/html/token.go 2026-10-22 15:49:22.000000000 +0200 @@ -156,6 +156,7 @@ // incremented on each call to TagAttr. pendingAttr [2]span attr [][2]span + attrNames map[string]bool nAttrReturned int // rawTag is the "script" in "</script>" that closes the next token. If // non-empty, the subsequent call to Next will return a raw or RCDATA text @@ -867,6 +868,7 @@ func (z *Tokenizer) readTag(saveAttr bool) { z.attr = z.attr[:0] z.nAttrReturned = 0 + clear(z.attrNames) // Read the tag name and attribute key/value pairs. z.readTagName() if z.skipWhiteSpace(); z.err != nil { @@ -880,9 +882,11 @@ z.raw.end-- z.readTagAttrKey() z.readTagAttrVal() - // Save pendingAttr if saveAttr and that attribute has a non-empty key. - if saveAttr && z.pendingAttr[0].start != z.pendingAttr[0].end { + // Save pendingAttr if saveAttr and that attribute has a non-empty key, and the key hasn't been seen before. + key := strings.ToLower(string(z.buf[z.pendingAttr[0].start:z.pendingAttr[0].end])) + if saveAttr && z.pendingAttr[0].start != z.pendingAttr[0].end && !z.attrNames[key] { z.attr = append(z.attr, z.pendingAttr) + z.attrNames[key] = true } if z.skipWhiteSpace(); z.err != nil { break @@ -1273,8 +1277,9 @@ // The input is assumed to be UTF-8 encoded. func NewTokenizerFragment(r io.Reader, contextTag string) *Tokenizer { z := &Tokenizer{ - r: r, - buf: make([]byte, 0, 4096), + r: r, + buf: make([]byte, 0, 4096), + attrNames: make(map[string]bool), } if contextTag != "" { switch s := strings.ToLower(contextTag); s { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/modules.txt new/vendor/modules.txt --- old/vendor/modules.txt 2026-10-05 17:50:42.000000000 +0200 +++ new/vendor/modules.txt 2026-10-22 15:49:22.000000000 +0200 @@ -128,7 +128,7 @@ # go.yaml.in/yaml/v3 v3.0.5 ## explicit; go 1.16 go.yaml.in/yaml/v3 -# golang.org/x/net v0.52.0 +# golang.org/x/net v0.55.0 ## explicit; go 1.25.0 golang.org/x/net/html golang.org/x/net/html/atom
