diff --git a/models/actions/run.go b/models/actions/run.go index 4f886999e9..e28242fec5 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" @@ -305,7 +306,7 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork } else { hasWaiting = true } - job.Name, _ = util.SplitStringAtByteN(job.Name, 255) + job.Name = base.EllipsisStringWholeWord(job.Name, 255) runJobs = append(runJobs, &ActionRunJob{ RunID: run.ID, RepoID: run.RepoID, diff --git a/models/actions/task.go b/models/actions/task.go index 856a43af4a..47b31d27e6 100644 --- a/models/actions/task.go +++ b/models/actions/task.go @@ -12,6 +12,7 @@ import ( auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -298,9 +299,8 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask if len(workflowJob.Steps) > 0 { steps := make([]*ActionTaskStep, len(workflowJob.Steps)) for i, v := range workflowJob.Steps { - name, _ := util.SplitStringAtByteN(v.String(), 255) steps[i] = &ActionTaskStep{ - Name: name, + Name: base.EllipsisStringWholeWord(v.String(), 255), TaskID: task.ID, Index: int64(i), RepoID: task.RepoID, diff --git a/modules/base/tool.go b/modules/base/tool.go index 9e43030f40..9e257d253f 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -19,6 +19,7 @@ import ( "strconv" "strings" "time" + "unicode" "unicode/utf8" "code.gitea.io/gitea/modules/git" @@ -131,6 +132,28 @@ func EllipsisString(str string, length int) string { return string([]rune(str)[:length-3]) + "..." } +// EllipsisStringWholeWord returns a truncated short string with … appended if the input is larger then the limit. +// If the input contains spaces the string is truncated at the last space before reaching the length limit. +// If the input does not contain a space before reaching the length limit, the input is truncated in the middle of a word. +func EllipsisStringWholeWord(str string, maxLength int) string { + lastSpace := -1 + length := 0 + for i, r := range str { + if unicode.IsSpace(r) { + lastSpace = i + } + length++ + if length > maxLength { + if lastSpace != -1 { + return str[:lastSpace] + "…" + } + return str[:i] + "…" + } + } + + return str +} + // TruncateString returns a truncated string with given limit, // it returns input string if length is not reached limit. func TruncateString(str string, limit int) string { diff --git a/modules/base/tool_test.go b/modules/base/tool_test.go index 4af8b9bc4d..46417ee095 100644 --- a/modules/base/tool_test.go +++ b/modules/base/tool_test.go @@ -129,6 +129,38 @@ func TestEllipsisString(t *testing.T) { assert.Equal(t, "测试文本一二三四", EllipsisString("测试文本一二三四", 10)) } +func TestEllipsisStringWholeWord(t *testing.T) { + assert.Equal(t, "…", EllipsisStringWholeWord("foobar", 0)) + assert.Equal(t, "f…", EllipsisStringWholeWord("foobar", 1)) + assert.Equal(t, "fo…", EllipsisStringWholeWord("foobar", 2)) + assert.Equal(t, "foo…", EllipsisStringWholeWord("foobar", 3)) + assert.Equal(t, "foob…", EllipsisStringWholeWord("foobar", 4)) + assert.Equal(t, "fooba…", EllipsisStringWholeWord("foobar", 5)) + assert.Equal(t, "foobar", EllipsisStringWholeWord("foobar", 6)) + assert.Equal(t, "foobar", EllipsisStringWholeWord("foobar", 10)) + + assert.Equal(t, "…", EllipsisStringWholeWord("foo bar", 0)) + assert.Equal(t, "f…", EllipsisStringWholeWord("foo bar", 1)) + assert.Equal(t, "fo…", EllipsisStringWholeWord("foo bar", 2)) + assert.Equal(t, "foo…", EllipsisStringWholeWord("foo bar", 3)) + assert.Equal(t, "foo…", EllipsisStringWholeWord("foo bar", 4)) + assert.Equal(t, "foo…", EllipsisStringWholeWord("foo bar", 5)) + assert.Equal(t, "foo…", EllipsisStringWholeWord("foo bar", 6)) + assert.Equal(t, "foo bar", EllipsisStringWholeWord("foo bar", 7)) + assert.Equal(t, "foo bar", EllipsisStringWholeWord("foo bar", 10)) + + assert.Equal(t, "foo bar…", EllipsisStringWholeWord("foo bar foo", 7)) + assert.Equal(t, "foo bar…", EllipsisStringWholeWord("foo bar foo", 8)) + assert.Equal(t, "foo bar…", EllipsisStringWholeWord("foo bar foo", 9)) + assert.Equal(t, "foo bar…", EllipsisStringWholeWord("foo bar foo", 10)) + assert.Equal(t, "foo bar foo", EllipsisStringWholeWord("foo bar foo", 11)) + + assert.Equal(t, "测试文本…", EllipsisStringWholeWord("测试文本一二三四", 4)) + assert.Equal(t, "测试文本…", EllipsisStringWholeWord("测试文本 一二三四", 6)) // contains special unicode space U+2008 + assert.Equal(t, "测试文本一二…", EllipsisStringWholeWord("测试文本一二三四", 6)) + assert.Equal(t, "测试文本一二三四", EllipsisStringWholeWord("测试文本一二三四", 10)) +} + func TestTruncateString(t *testing.T) { assert.Equal(t, "", TruncateString("foobar", 0)) assert.Equal(t, "f", TruncateString("foobar", 1)) diff --git a/modules/issue/template/unmarshal.go b/modules/issue/template/unmarshal.go index 0fc13d7ddf..1c1e60891d 100644 --- a/modules/issue/template/unmarshal.go +++ b/modules/issue/template/unmarshal.go @@ -9,11 +9,11 @@ import ( "path" "strconv" + "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" "gopkg.in/yaml.v3" ) @@ -109,7 +109,7 @@ func unmarshal(filename string, content []byte) (*api.IssueTemplate, error) { it.Content = string(content) it.Name = path.Base(it.FileName) // paths in Git are always '/' separated - do not use filepath! - it.About, _ = util.SplitStringAtByteN(it.Content, 80) + it.About = base.EllipsisStringWholeWord(it.Content, 80) } else { it.Content = templateBody if it.About == "" { diff --git a/routers/api/actions/runner/runner.go b/routers/api/actions/runner/runner.go index d4078d8af2..b9525dd850 100644 --- a/routers/api/actions/runner/runner.go +++ b/routers/api/actions/runner/runner.go @@ -12,8 +12,8 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/actions" + "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" actions_service "code.gitea.io/gitea/services/actions" runnerv1 "code.gitea.io/actions-proto-go/runner/v1" @@ -69,10 +69,9 @@ func (s *Service) Register( labels := req.Msg.Labels // create new runner - name, _ := util.SplitStringAtByteN(req.Msg.Name, 255) runner := &actions_model.ActionRunner{ UUID: gouuid.New().String(), - Name: name, + Name: base.EllipsisStringWholeWord(req.Msg.Name, 255), OwnerID: runnerToken.OwnerID, RepoID: runnerToken.RepoID, Version: req.Msg.Version, diff --git a/services/feed/action.go b/services/feed/action.go index 83daaa1438..0edef1cc26 100644 --- a/services/feed/action.go +++ b/services/feed/action.go @@ -13,11 +13,11 @@ import ( issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/repository" - "code.gitea.io/gitea/modules/util" notify_service "code.gitea.io/gitea/services/notify" ) @@ -109,15 +109,7 @@ func (a *actionNotifier) CreateIssueComment(ctx context.Context, doer *user_mode IsPrivate: issue.Repo.IsPrivate, } - truncatedContent, truncatedRight := util.SplitStringAtByteN(comment.Content, 200) - if truncatedRight != "" { - // in case the content is in a Latin family language, we remove the last broken word. - lastSpaceIdx := strings.LastIndex(truncatedContent, " ") - if lastSpaceIdx != -1 && (len(truncatedContent)-lastSpaceIdx < 15) { - truncatedContent = truncatedContent[:lastSpaceIdx] + "…" - } - } - act.Content = fmt.Sprintf("%d|%s", issue.Index, truncatedContent) + act.Content = fmt.Sprintf("%d|%s", issue.Index, base.EllipsisStringWholeWord(comment.Content, 200)) if issue.IsPull { act.OpType = activities_model.ActionCommentPull