Skip to content

Commit 71f75a0

Browse files
authored
Merge pull request #1235 from wakatime/develop
Release v1.131.0
2 parents 885c06c + 14e8932 commit 71f75a0

File tree

12 files changed

+163
-23
lines changed

12 files changed

+163
-23
lines changed

USAGE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ exclude_unknown_project = false
3333
status_bar_enabled = true
3434
status_bar_coding_activity = true
3535
status_bar_hide_categories = false
36+
status_bar_max_categories = 0
3637
offline = true
3738
proxy = https://user:pass@localhost:8080
3839
no_ssl_verify = false
@@ -100,6 +101,7 @@ Notice how you have to include the exclude patterns from your main `~/.wakatime.
100101
| status_bar_enabled | Turns on wakatime status bar for certain editors. | _bool_ | `true` |
101102
| status_bar_coding_activity | Enables displaying Today's code stats in the status bar of some editors. When false, only the WakaTime icon is displayed in the status bar. | _bool_ | `true` |
102103
| status_bar_hide_categories | When `true`, --today only displays the total code stats, never displaying Categories in the output. | _bool_ | `false` |
104+
| status_bar_max_categories | When greater than zero, limits the number of categories displayed in the status bar. | _int_ | `0` |
103105
| offline | Enables saving code stats locally to ~/.wakatime/offline_heartbeats.bdb when offline, and syncing to the dashboard later when back online. | _bool_ | `true` |
104106
| proxy | Optional proxy configuration. Supports HTTPS, SOCKS and NTLM proxies. For ex: `https://user:pass@host:port`, `socks5://user:pass@host:port`, `domain\\user:pass` | _string_ | |
105107
| no_ssl_verify | Disables SSL certificate verification for HTTPS requests. By default, SSL certificates are verified. | _bool_ | `false` |

cmd/today/today.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func Today(ctx context.Context, v *viper.Viper) (string, error) {
5858
return "", fmt.Errorf("failed fetching today from api: %w", err)
5959
}
6060

61-
output, err := summary.RenderToday(s, paramStatusBar.HideCategories, paramStatusBar.Output)
61+
output, err := summary.RenderToday(s, paramStatusBar.HideCategories, paramStatusBar.MaxCategories, paramStatusBar.Output)
6262
if err != nil {
6363
return "", fmt.Errorf("failed generating today output: %s", err)
6464
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ require (
3535
github.com/alecthomas/repr v0.4.0 // indirect
3636
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
3737
github.com/fsnotify/fsnotify v1.9.0 // indirect
38-
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
38+
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
3939
github.com/golang/mock v1.6.0 // indirect
4040
github.com/inconshreveable/mousetrap v1.1.0 // indirect
4141
github.com/juju/errors v1.0.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ github.com/go-viper/encoding/ini v0.1.1 h1:MVWY7B2XNw7lnOqHutGRc97bF3rP7omOdgjdM
3232
github.com/go-viper/encoding/ini v0.1.1/go.mod h1:Pfi4M2V1eAGJVZ5q6FrkHPhtHED2YgLlXhvgMVrB+YQ=
3333
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
3434
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
35+
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
36+
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
3537
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
3638
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
3739
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=

pkg/params/params.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ var (
150150
FlagReadOrderFlagPrecedence: {"today-hide-categories", "settings.status_bar_hide_categories"},
151151
FlagReadOrderProjectConfigPrecedence: {"settings.status_bar_hide_categories", "today-hide-categories"},
152152
}
153+
todayMaxCategoriesOrder = map[FlagReadOrder][]string{
154+
FlagReadOrderFlagPrecedence: {"today-max-categories", "settings.status_bar_max_categories"},
155+
FlagReadOrderProjectConfigPrecedence: {"settings.status_bar_max_categories", "today-max-categories"},
156+
}
153157
)
154158

155159
type (
@@ -262,6 +266,7 @@ type (
262266
// StatusBar contains status bar related parameters.
263267
StatusBar struct {
264268
HideCategories bool
269+
MaxCategories int
265270
Output output.Output
266271
}
267272
)
@@ -811,6 +816,21 @@ func LoadStatusBarParams(v *viper.Viper, order FlagReadOrder) (StatusBar, error)
811816
hideCategories = val
812817
}
813818

819+
maxCategories := 2
820+
821+
if maxCategoriesStr := vipertools.FirstNonEmptyString(v, todayMaxCategoriesOrder[order]...); maxCategoriesStr != "" {
822+
val, err := strconv.Atoi(maxCategoriesStr)
823+
if err != nil {
824+
return StatusBar{}, fmt.Errorf("failed to parse today-max-categories: %s", err)
825+
}
826+
827+
if val < 0 {
828+
return StatusBar{}, fmt.Errorf("today-max-categories must be a positive number, got %d", val)
829+
}
830+
831+
maxCategories = val
832+
}
833+
814834
var out output.Output
815835

816836
if outputStr := vipertools.GetString(v, "output"); outputStr != "" {
@@ -824,6 +844,7 @@ func LoadStatusBarParams(v *viper.Viper, order FlagReadOrder) (StatusBar, error)
824844

825845
return StatusBar{
826846
HideCategories: hideCategories,
847+
MaxCategories: maxCategories,
827848
Output: out,
828849
}, nil
829850
}
@@ -1248,8 +1269,9 @@ func (p SanitizeParams) String() string {
12481269
// String implements fmt.Stringer interface.
12491270
func (p StatusBar) String() string {
12501271
return fmt.Sprintf(
1251-
"hide categories: %t, output: '%s'",
1272+
"hide categories: %t, max categories: %d, output: '%s'",
12521273
p.HideCategories,
1274+
p.MaxCategories,
12531275
p.Output,
12541276
)
12551277
}

pkg/params/params_test.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2919,6 +2919,46 @@ func TestLoadStatusBarParams_Output_Invalid(t *testing.T) {
29192919
assert.Equal(t, "failed to parse output: invalid output \"invalid\"", err.Error())
29202920
}
29212921

2922+
func TestLoadStatusBarParams_MaxCategories(t *testing.T) {
2923+
v := vipertools.MustNew()
2924+
v.Set("today-max-categories", 0)
2925+
2926+
params, err := paramspkg.LoadStatusBarParams(v, paramspkg.FlagReadOrderFlagPrecedence)
2927+
require.NoError(t, err)
2928+
2929+
assert.Equal(t, output.TextOutput, params.Output)
2930+
}
2931+
2932+
func TestLoadStatusBarParams_MaxCategoriesNegative(t *testing.T) {
2933+
v := vipertools.MustNew()
2934+
v.Set("today-max-categories", -1)
2935+
2936+
_, err := paramspkg.LoadStatusBarParams(v, paramspkg.FlagReadOrderFlagPrecedence)
2937+
require.Error(t, err)
2938+
2939+
assert.Equal(t, "today-max-categories must be a positive number, got -1", err.Error())
2940+
}
2941+
2942+
func TestLoadStatusBarParams_MaxCategoriesSetting(t *testing.T) {
2943+
v := vipertools.MustNew()
2944+
v.Set("settings.status_bar_max_categories", 1)
2945+
2946+
params, err := paramspkg.LoadStatusBarParams(v, paramspkg.FlagReadOrderFlagPrecedence)
2947+
require.NoError(t, err)
2948+
2949+
assert.Equal(t, output.TextOutput, params.Output)
2950+
}
2951+
2952+
func TestLoadStatusBarParams_MaxCategoriesNegativeSetting(t *testing.T) {
2953+
v := vipertools.MustNew()
2954+
v.Set("settings.status_bar_max_categories", -1)
2955+
2956+
_, err := paramspkg.LoadStatusBarParams(v, paramspkg.FlagReadOrderFlagPrecedence)
2957+
require.Error(t, err)
2958+
2959+
assert.Equal(t, "today-max-categories must be a positive number, got -1", err.Error())
2960+
}
2961+
29222962
func TestLoadHeartbeatParams_ExtraHeartbeats_StdinReadOnlyOnce(t *testing.T) {
29232963
r, w, err := os.Pipe()
29242964
require.NoError(t, err)
@@ -3159,7 +3199,7 @@ func TestStatusBar_String(t *testing.T) {
31593199

31603200
assert.Equal(
31613201
t,
3162-
"hide categories: true, output: 'json'",
3202+
"hide categories: true, max categories: 0, output: 'json'",
31633203
statusbar.String(),
31643204
)
31653205
}

pkg/summary/summary.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ type (
158158
// RenderToday generates a text representation from summary of the current day.
159159
// If out is set to output.RawJSONOutput or output.JSONOutput, the summary will be marshaled to JSON.
160160
// Expects exactly one summary for the current day. Will return an error otherwise.
161-
func RenderToday(summary *Summary, hideCategories bool, out output.Output) (string, error) {
161+
func RenderToday(summary *Summary, hideCategories bool, maxCategories int, out output.Output) (string, error) {
162162
if summary == nil {
163163
return "", errors.New("no summary found for the current day")
164164
}
@@ -179,7 +179,7 @@ func RenderToday(summary *Summary, hideCategories bool, out output.Output) (stri
179179
}
180180

181181
s := simplified{
182-
Text: getText(summary, hideCategories),
182+
Text: getText(summary, hideCategories, maxCategories),
183183
HasTeamFeatures: summary.HasTeamFeatures,
184184
}
185185

@@ -191,18 +191,31 @@ func RenderToday(summary *Summary, hideCategories bool, out output.Output) (stri
191191
return string(data), nil
192192
}
193193

194-
return getText(summary, hideCategories), nil
194+
return getText(summary, hideCategories, maxCategories), nil
195195
}
196196

197-
func getText(summary *Summary, hideCategories bool) string {
197+
func getText(summary *Summary, hideCategories bool, maxCategories int) string {
198198
if len(summary.Data.Categories) < 2 || hideCategories {
199199
return summary.Data.GrandTotal.Text
200200
}
201201

202202
var outputs []string
203-
for _, category := range summary.Data.Categories {
203+
204+
categories := summary.Data.Categories
205+
206+
if maxCategories > 0 && len(categories) > maxCategories {
207+
categories = categories[:maxCategories]
208+
}
209+
210+
for _, category := range categories {
204211
outputs = append(outputs, fmt.Sprintf("%s %s", category.Text, category.Name))
205212
}
206213

207-
return strings.Join(outputs, ", ")
214+
result := strings.Join(outputs, ", ")
215+
216+
if maxCategories > 1 && len(summary.Data.Categories) > maxCategories {
217+
result += "..."
218+
}
219+
220+
return result
208221
}

pkg/summary/summary_test.go

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,56 @@ import (
1414

1515
func TestRenderToday(t *testing.T) {
1616
tests := map[string]struct {
17-
Output output.Output
18-
Expected string
17+
Output output.Output
18+
MaxCategories int
19+
Expected string
1920
}{
2021
"text output": {
21-
Output: output.TextOutput,
22-
Expected: "2 hrs 17 mins Coding, 7 secs Debugging",
22+
Output: output.TextOutput,
23+
MaxCategories: 0,
24+
Expected: "2 hrs 17 mins Coding, 7 secs Debugging, 6 secs AI Coding",
25+
},
26+
"text output truncated 2": {
27+
Output: output.TextOutput,
28+
MaxCategories: 2,
29+
Expected: "2 hrs 17 mins Coding, 7 secs Debugging...",
30+
},
31+
"text output truncated 1": {
32+
Output: output.TextOutput,
33+
MaxCategories: 1,
34+
Expected: "2 hrs 17 mins Coding",
2335
},
2436
"json output": {
25-
Output: output.JSONOutput,
26-
Expected: readFile(t, "testdata/statusbar_today_simplified.json"),
37+
Output: output.JSONOutput,
38+
MaxCategories: 0,
39+
Expected: readFile(t, "testdata/statusbar_today_simplified.json"),
40+
},
41+
"json output truncated 2": {
42+
Output: output.JSONOutput,
43+
MaxCategories: 2,
44+
Expected: readFile(t, "testdata/statusbar_today_simplified_truncated_2.json"),
45+
},
46+
"json output truncated 1": {
47+
Output: output.JSONOutput,
48+
MaxCategories: 1,
49+
Expected: readFile(t, "testdata/statusbar_today_simplified_truncated_1.json"),
2750
},
2851
"raw json output": {
29-
Output: output.RawJSONOutput,
30-
Expected: readFile(t, "testdata/statusbar_today.json"),
52+
Output: output.RawJSONOutput,
53+
MaxCategories: 0,
54+
Expected: readFile(t, "testdata/statusbar_today.json"),
55+
},
56+
"raw json output not truncated": {
57+
Output: output.RawJSONOutput,
58+
MaxCategories: 1,
59+
Expected: readFile(t, "testdata/statusbar_today.json"),
3160
},
3261
}
3362

3463
for name, test := range tests {
3564
t.Run(name, func(t *testing.T) {
36-
rendered, err := summary.RenderToday(testSummary(), false, test.Output)
65+
s := testSummary()
66+
rendered, err := summary.RenderToday(s, false, test.MaxCategories, test.Output)
3767
require.NoError(t, err)
3868

3969
assert.Equal(t, test.Expected, rendered)
@@ -45,14 +75,32 @@ func TestRenderToday_OneCategory(t *testing.T) {
4575
s := testSummary()
4676
s.Data.Categories = s.Data.Categories[:1]
4777

48-
rendered, err := summary.RenderToday(s, false, output.TextOutput)
78+
rendered, err := summary.RenderToday(s, false, 1, output.TextOutput)
4979
require.NoError(t, err)
5080

5181
assert.Equal(t, "2 hrs 17 mins", rendered)
5282
}
5383

84+
func TestRenderToday_OneCategoryTruncated(t *testing.T) {
85+
s := testSummary()
86+
87+
rendered, err := summary.RenderToday(s, false, 1, output.TextOutput)
88+
require.NoError(t, err)
89+
90+
assert.Equal(t, "2 hrs 17 mins Coding", rendered)
91+
}
92+
93+
func TestRenderToday_Truncated(t *testing.T) {
94+
s := testSummary()
95+
96+
rendered, err := summary.RenderToday(s, false, 2, output.TextOutput)
97+
require.NoError(t, err)
98+
99+
assert.Equal(t, "2 hrs 17 mins Coding, 7 secs Debugging...", rendered)
100+
}
101+
54102
func TestRenderToday_MultipleCategoriesHidden(t *testing.T) {
55-
rendered, err := summary.RenderToday(testSummary(), true, output.TextOutput)
103+
rendered, err := summary.RenderToday(testSummary(), true, 0, output.TextOutput)
56104
require.NoError(t, err)
57105

58106
assert.Equal(t, "2 hrs 17 mins", rendered)
@@ -92,6 +140,17 @@ func testSummary() *summary.Summary {
92140
Text: "7 secs",
93141
TotalSeconds: 7.100772,
94142
},
143+
{
144+
Decimal: "0.00",
145+
Digital: "0:00:06",
146+
Hours: 0,
147+
Minutes: 0,
148+
Name: "AI Coding",
149+
Percent: 0.08,
150+
Seconds: 6,
151+
Text: "6 secs",
152+
TotalSeconds: 6.100772,
153+
},
95154
},
96155
Dependencies: []summary.Dependency{
97156
{
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"cached_at":"2023-01-29T17:32:05Z","data":{"categories":[{"decimal":"2.28","digital":"2:17:36","hours":2,"minutes":17,"name":"Coding","percent":99.02,"seconds":36,"text":"2 hrs 17 mins","total_seconds":8256.598234},{"decimal":"0.00","digital":"0:00:07","hours":0,"minutes":0,"name":"Debugging","percent":0.08,"seconds":7,"text":"7 secs","total_seconds":7.100772}],"dependencies":[{"decimal":"1.25","digital":"1:15:44","hours":1,"minutes":15,"name":"strings","percent":64.82,"seconds":44,"text":"1 hr 15 mins","total_seconds":4544.055638},{"decimal":"0.82","digital":"0:49:06","hours":0,"minutes":49,"name":"io","percent":35.18,"seconds":6,"text":"49 mins","total_seconds":2946.01205}],"editors":[{"decimal":"2.07","digital":"2:04:07","hours":2,"minutes":4,"name":"VS Code","percent":90.2,"seconds":7,"text":"2 hrs 4 mins","total_seconds":7447.112447},{"decimal":"0.22","digital":"0:13:29","hours":0,"minutes":13,"name":"Zsh-Wakatime-Sobolevn","percent":9.8,"seconds":29,"text":"13 mins","total_seconds":809.485787}],"grand_total":{"decimal":"2.28","digital":"2:17","hours":2,"minutes":17,"text":"2 hrs 17 mins","total_seconds":8256.598234},"languages":[{"decimal":"1.93","digital":"1:56:49","hours":1,"minutes":56,"name":"Go","percent":86.15,"seconds":49,"text":"1 hr 56 mins","total_seconds":7009.317188},{"decimal":"0.27","digital":"0:16:11","hours":0,"minutes":16,"name":"Other","percent":13.85,"seconds":11,"text":"16 mins","total_seconds":971.489169}],"machines":[{"decimal":"2.28","digital":"2:17:36","hours":2,"machine_name_id":"370471e8-b6dd-41aa-a94e-d4fb59a7db85","minutes":17,"name":"WakaMachine","percent":100,"seconds":36,"text":"2 hrs 17 mins","total_seconds":8256.598234}],"operating_systems":[{"decimal":"2.28","digital":"2:17:36","hours":2,"minutes":17,"name":"Mac","percent":100,"seconds":36,"text":"2 hrs 17 mins","total_seconds":8256.598234}],"projects":[{"decimal":"2.05","digital":"2:03:44","hours":2,"minutes":3,"name":"wakatime-cli","percent":97.53,"seconds":44,"text":"2 hrs 3 mins","total_seconds":7424.621273},{"decimal":"0.05","digital":"0:03:02","hours":0,"minutes":3,"name":"Terminal","percent":2.46,"seconds":2,"text":"3 mins","total_seconds":182.934009}],"range":{"date":"2023-01-29","end":"2023-01-30T02:59:59Z","start":"2023-01-29T03:00:00Z","text":"Sun Jan 29th 2023","timezone":"America/Sao_Paulo"}},"has_team_features":true}
1+
{"cached_at":"2023-01-29T17:32:05Z","data":{"categories":[{"decimal":"2.28","digital":"2:17:36","hours":2,"minutes":17,"name":"Coding","percent":99.02,"seconds":36,"text":"2 hrs 17 mins","total_seconds":8256.598234},{"decimal":"0.00","digital":"0:00:07","hours":0,"minutes":0,"name":"Debugging","percent":0.08,"seconds":7,"text":"7 secs","total_seconds":7.100772},{"decimal":"0.00","digital":"0:00:06","hours":0,"minutes":0,"name":"AI Coding","percent":0.08,"seconds":6,"text":"6 secs","total_seconds":6.100772}],"dependencies":[{"decimal":"1.25","digital":"1:15:44","hours":1,"minutes":15,"name":"strings","percent":64.82,"seconds":44,"text":"1 hr 15 mins","total_seconds":4544.055638},{"decimal":"0.82","digital":"0:49:06","hours":0,"minutes":49,"name":"io","percent":35.18,"seconds":6,"text":"49 mins","total_seconds":2946.01205}],"editors":[{"decimal":"2.07","digital":"2:04:07","hours":2,"minutes":4,"name":"VS Code","percent":90.2,"seconds":7,"text":"2 hrs 4 mins","total_seconds":7447.112447},{"decimal":"0.22","digital":"0:13:29","hours":0,"minutes":13,"name":"Zsh-Wakatime-Sobolevn","percent":9.8,"seconds":29,"text":"13 mins","total_seconds":809.485787}],"grand_total":{"decimal":"2.28","digital":"2:17","hours":2,"minutes":17,"text":"2 hrs 17 mins","total_seconds":8256.598234},"languages":[{"decimal":"1.93","digital":"1:56:49","hours":1,"minutes":56,"name":"Go","percent":86.15,"seconds":49,"text":"1 hr 56 mins","total_seconds":7009.317188},{"decimal":"0.27","digital":"0:16:11","hours":0,"minutes":16,"name":"Other","percent":13.85,"seconds":11,"text":"16 mins","total_seconds":971.489169}],"machines":[{"decimal":"2.28","digital":"2:17:36","hours":2,"machine_name_id":"370471e8-b6dd-41aa-a94e-d4fb59a7db85","minutes":17,"name":"WakaMachine","percent":100,"seconds":36,"text":"2 hrs 17 mins","total_seconds":8256.598234}],"operating_systems":[{"decimal":"2.28","digital":"2:17:36","hours":2,"minutes":17,"name":"Mac","percent":100,"seconds":36,"text":"2 hrs 17 mins","total_seconds":8256.598234}],"projects":[{"decimal":"2.05","digital":"2:03:44","hours":2,"minutes":3,"name":"wakatime-cli","percent":97.53,"seconds":44,"text":"2 hrs 3 mins","total_seconds":7424.621273},{"decimal":"0.05","digital":"0:03:02","hours":0,"minutes":3,"name":"Terminal","percent":2.46,"seconds":2,"text":"3 mins","total_seconds":182.934009}],"range":{"date":"2023-01-29","end":"2023-01-30T02:59:59Z","start":"2023-01-29T03:00:00Z","text":"Sun Jan 29th 2023","timezone":"America/Sao_Paulo"}},"has_team_features":true}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"text":"2 hrs 17 mins Coding, 7 secs Debugging","has_team_features":true}
1+
{"text":"2 hrs 17 mins Coding, 7 secs Debugging, 6 secs AI Coding","has_team_features":true}

0 commit comments

Comments
 (0)