forgejo_backup/modules/indexer/issues/internal/qstring.go
Danko Aleksejevs 905a5748a8 Add issue number to the search index, rank number and title matches higher (#7956) (#7968)
An attempt at solving #7956. This (and rebuilding the index) seems enough to ensure the issue *appears* among the results.

However, I couldn't figure out from [bleve docs](https://github.com/blevesearch/bleve/blob/master/docs/scoring.md) how to affect the scoring based on specific fields, or whether that is possible at all.

Disclaimer: I've never written Go before, sorry 😅 take it as a quick PoC more than anything.

### Tests

- I added test coverage for Go changes...
  - [x] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] I do not want this change to show in the release notes.
- [x] I want the title to show in the release notes with a link to this pull request.
- [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.

<!--start release-notes-assistant-->

## Release notes
<!--URL:https://codeberg.org/forgejo/forgejo-->
- Features
  - [PR](https://codeberg.org/forgejo/forgejo/pulls/7968): <!--number 7968 --><!--line 0 --><!--description QWRkIGlzc3VlIG51bWJlciB0byB0aGUgc2VhcmNoIGluZGV4LCByYW5rIG51bWJlciBhbmQgdGl0bGUgbWF0Y2hlcyBoaWdoZXIgKCM3OTU2KQ==-->Add issue number to the search index, rank number and title matches higher (#7956)<!--description-->
<!--end release-notes-assistant-->

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7968
Reviewed-by: Shiny Nematoda <snematoda@noreply.codeberg.org>
Co-authored-by: Danko Aleksejevs <danko@very.lv>
Co-committed-by: Danko Aleksejevs <danko@very.lv>
2025-06-04 07:42:29 +02:00

121 lines
2.1 KiB
Go

// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package internal
import (
"io"
"strconv"
"strings"
)
type BoolOpt int
const (
BoolOptMust BoolOpt = iota
BoolOptShould
BoolOptNot
)
type Token struct {
Term string
Kind BoolOpt
Fuzzy bool
}
func (tk *Token) ParseIssueReference() (int64, error) {
term := tk.Term
if term[0] == '#' || term[0] == '!' {
term = term[1:]
}
return strconv.ParseInt(term, 10, 64)
}
type Tokenizer struct {
in *strings.Reader
}
func (t *Tokenizer) next() (tk Token, err error) {
var (
sb strings.Builder
r rune
)
tk.Kind = BoolOptShould
tk.Fuzzy = true
// skip all leading white space
for {
if r, _, err = t.in.ReadRune(); err == nil && r == ' ' {
//nolint:staticcheck,wastedassign // SA4006 the variable is used after the loop
r, _, err = t.in.ReadRune()
continue
}
break
}
if err != nil {
return tk, err
}
// check for +/- op, increment to the next rune in both cases
switch r {
case '+':
tk.Kind = BoolOptMust
r, _, err = t.in.ReadRune()
case '-':
tk.Kind = BoolOptNot
r, _, err = t.in.ReadRune()
}
if err != nil {
return tk, err
}
// parse the string, escaping special characters
for esc := false; err == nil; r, _, err = t.in.ReadRune() {
if esc {
if !strings.ContainsRune("+-\\\"", r) {
sb.WriteRune('\\')
}
sb.WriteRune(r)
esc = false
continue
}
switch r {
case '\\':
esc = true
case '"':
if !tk.Fuzzy {
goto nextEnd
}
tk.Fuzzy = false
case ' ', '\t':
if tk.Fuzzy {
goto nextEnd
}
sb.WriteRune(r)
default:
sb.WriteRune(r)
}
}
nextEnd:
tk.Term = sb.String()
if err == io.EOF {
err = nil
} // do not consider EOF as an error at the end
return tk, err
}
// Tokenize the keyword
func (o *SearchOptions) Tokens() (tokens []Token, err error) {
in := strings.NewReader(o.Keyword)
it := Tokenizer{in: in}
for token, err := it.next(); err == nil; token, err = it.next() {
tokens = append(tokens, token)
}
if err != nil && err != io.EOF {
return nil, err
}
return tokens, nil
}