diff --git a/cmd/heartbeat/heartbeat.go b/cmd/heartbeat/heartbeat.go index de289e26..ef8847b7 100644 --- a/cmd/heartbeat/heartbeat.go +++ b/cmd/heartbeat/heartbeat.go @@ -185,6 +185,8 @@ func buildHeartbeats(params paramscmd.Params) []heartbeat.Heartbeat { params.Heartbeat.IsWrite, params.Heartbeat.Language, params.Heartbeat.LanguageAlternate, + params.Heartbeat.LineAdditions, + params.Heartbeat.LineDeletions, params.Heartbeat.LineNumber, params.Heartbeat.LinesInFile, params.Heartbeat.LocalFile, @@ -210,6 +212,8 @@ func buildHeartbeats(params paramscmd.Params) []heartbeat.Heartbeat { h.IsWrite, h.Language, h.LanguageAlternate, + h.LineAdditions, + h.LineDeletions, h.LineNumber, h.Lines, h.LocalFile, diff --git a/cmd/offline/offline.go b/cmd/offline/offline.go index a837ff72..5680e4f9 100644 --- a/cmd/offline/offline.go +++ b/cmd/offline/offline.go @@ -90,6 +90,8 @@ func buildHeartbeats(params paramscmd.Params) []heartbeat.Heartbeat { params.Heartbeat.IsWrite, params.Heartbeat.Language, params.Heartbeat.LanguageAlternate, + params.Heartbeat.LineAdditions, + params.Heartbeat.LineDeletions, params.Heartbeat.LineNumber, params.Heartbeat.LinesInFile, params.Heartbeat.LocalFile, @@ -115,6 +117,8 @@ func buildHeartbeats(params paramscmd.Params) []heartbeat.Heartbeat { h.IsWrite, h.Language, h.LanguageAlternate, + h.LineAdditions, + h.LineDeletions, h.LineNumber, h.Lines, h.LocalFile, diff --git a/cmd/params/params.go b/cmd/params/params.go index 1ae51b89..acadc0a6 100644 --- a/cmd/params/params.go +++ b/cmd/params/params.go @@ -86,6 +86,8 @@ type ( IsWrite any `json:"is_write"` Language *string `json:"language"` LanguageAlternate string `json:"alternate_language"` + LineAdditions any `json:"line_additions"` + LineDeletions any `json:"line_deletions"` LineNumber any `json:"lineno"` Lines any `json:"lines"` Project string `json:"project"` @@ -106,6 +108,8 @@ type ( IsWrite *bool Language *string LanguageAlternate string + LineAdditions *int + LineDeletions *int LineNumber *int LinesInFile *int LocalFile string @@ -401,6 +405,16 @@ func LoadHeartbeatParams(v *viper.Viper) (Heartbeat, error) { isWrite = heartbeat.PointerTo(b) } + var lineAdditions *int + if num := v.GetInt("line-additions"); v.IsSet("line-additions") { + lineAdditions = heartbeat.PointerTo(num) + } + + var lineDeletions *int + if num := v.GetInt("line-deletions"); v.IsSet("line-deletions") { + lineDeletions = heartbeat.PointerTo(num) + } + var lineNumber *int if num := v.GetInt("lineno"); v.IsSet("lineno") { lineNumber = heartbeat.PointerTo(num) @@ -442,6 +456,8 @@ func LoadHeartbeatParams(v *viper.Viper) (Heartbeat, error) { IsWrite: isWrite, Language: language, LanguageAlternate: vipertools.GetString(v, "alternate-language"), + LineAdditions: lineAdditions, + LineDeletions: lineDeletions, LineNumber: lineNumber, LinesInFile: linesInFile, LocalFile: vipertools.GetString(v, "local-file"), @@ -727,6 +743,12 @@ func readExtraHeartbeats() ([]heartbeat.Heartbeat, error) { } func parseExtraHeartbeats(data string) ([]heartbeat.Heartbeat, error) { + if data == "" { + log.Debugln("skipping extra heartbeats, as no data was provided") + + return nil, nil + } + var extraHeartbeats []ExtraHeartbeat err := json.Unmarshal([]byte(data), &extraHeartbeats) @@ -965,6 +987,16 @@ func (p Heartbeat) String() string { language = *p.Language } + var lineAdditions string + if p.LineAdditions != nil { + lineAdditions = strconv.Itoa(*p.LineAdditions) + } + + var lineDeletions string + if p.LineDeletions != nil { + lineDeletions = strconv.Itoa(*p.LineDeletions) + } + var lineNumber string if p.LineNumber != nil { lineNumber = strconv.Itoa(*p.LineNumber) @@ -978,8 +1010,9 @@ func (p Heartbeat) String() string { return fmt.Sprintf( "category: '%s', cursor position: '%s', entity: '%s', entity type: '%s',"+ " num extra heartbeats: %d, guess language: %t, is unsaved entity: %t,"+ - " is write: %t, language: '%s', line number: '%s', lines in file: '%s',"+ - " time: %.5f, filter params: (%s), project params: (%s), sanitize params: (%s)", + " is write: %t, language: '%s', line additions: '%s', line deletions: '%s',"+ + " line number: '%s', lines in file: '%s', time: %.5f, filter params: (%s),"+ + " project params: (%s), sanitize params: (%s)", p.Category, cursorPosition, p.Entity, @@ -989,6 +1022,8 @@ func (p Heartbeat) String() string { p.IsUnsavedEntity, isWrite, language, + lineAdditions, + lineDeletions, lineNumber, linesInFile, p.Time, diff --git a/cmd/params/params_test.go b/cmd/params/params_test.go index caf8ed11..0eac4073 100644 --- a/cmd/params/params_test.go +++ b/cmd/params/params_test.go @@ -3,6 +3,7 @@ package params_test import ( "bytes" "fmt" + "io" "os" "path/filepath" "regexp" @@ -16,6 +17,7 @@ import ( "github.com/wakatime/wakatime-cli/pkg/apikey" "github.com/wakatime/wakatime-cli/pkg/heartbeat" inipkg "github.com/wakatime/wakatime-cli/pkg/ini" + "github.com/wakatime/wakatime-cli/pkg/log" "github.com/wakatime/wakatime-cli/pkg/output" "github.com/wakatime/wakatime-cli/pkg/project" "github.com/wakatime/wakatime-cli/pkg/regex" @@ -425,6 +427,46 @@ func TestLoadParams_ExtraHeartbeats_WithEOF(t *testing.T) { }, params.ExtraHeartbeats) } +func TestLoadParams_ExtraHeartbeats_NoData(t *testing.T) { + r, w, err := os.Pipe() + require.NoError(t, err) + + defer func() { + r.Close() + w.Close() + }() + + logs := bytes.NewBuffer(nil) + + teardownLogCapture := captureLogs(logs) + defer teardownLogCapture() + + origStdin := os.Stdin + + defer func() { os.Stdin = origStdin }() + + os.Stdin = r + + go func() { + _, err := w.Write([]byte{}) + require.NoError(t, err) + + w.Close() + }() + + v := viper.New() + v.Set("entity", "/path/to/file") + v.Set("extra-heartbeats", true) + + params, err := paramscmd.LoadHeartbeatParams(v) + require.NoError(t, err) + + assert.Empty(t, params.ExtraHeartbeats) + + assert.Contains(t, logs.String(), "skipping extra heartbeats, as no data was provided") + assert.NotContains(t, logs.String(), "failed to read extra heartbeats: failed parsing") +} + func TestLoadHeartbeat_GuessLanguage_FlagTakesPrecedence(t *testing.T) { v := viper.New() v.Set("entity", "/path/to/file") @@ -2419,6 +2461,8 @@ func TestHeartbeat_String(t *testing.T) { IsUnsavedEntity: true, IsWrite: heartbeat.PointerTo(true), Language: heartbeat.PointerTo("Golang"), + LineAdditions: heartbeat.PointerTo(123), + LineDeletions: heartbeat.PointerTo(456), LineNumber: heartbeat.PointerTo(4), LinesInFile: heartbeat.PointerTo(56), Time: 1585598059, @@ -2428,8 +2472,9 @@ func TestHeartbeat_String(t *testing.T) { t, "category: 'coding', cursor position: '15', entity: 'path/to/entity.go', entity type: 'file',"+ " num extra heartbeats: 3, guess language: true, is unsaved entity: true, is write: true,"+ - " language: 'Golang', line number: '4', lines in file: '56', time: 1585598059.00000, filter"+ - " params: (exclude: '[]', exclude unknown project: false, include: '[]', include only with"+ + " language: 'Golang', line additions: '123', line deletions: '456', line number: '4',"+ + " lines in file: '56', time: 1585598059.00000, filter params: (exclude: '[]',"+ + " exclude unknown project: false, include: '[]', include only with"+ " project file: false), project params: (alternate: '', branch alternate: '', map patterns:"+ " '[]', override: '', git submodules disabled: '[]', git submodule project map: '[]'), sanitize"+ " params: (hide branch names: '[]', hide project folder: false, hide file names: '[]',"+ @@ -2520,3 +2565,19 @@ func TestLoadParams_APIKeyPrefixSupported(t *testing.T) { _, err := paramscmd.LoadAPIParams(v) require.NoError(t, err) } + +func captureLogs(dest io.Writer) func() { + // set verbose + log.SetVerbose(true) + + logOutput := log.Output() + + // will write to log output and dest + mw := io.MultiWriter(logOutput, dest) + + log.SetOutput(mw) + + return func() { + log.SetOutput(logOutput) + } +} diff --git a/cmd/root.go b/cmd/root.go index 4975cd8d..1be235e3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -153,6 +153,8 @@ func setFlags(cmd *cobra.Command, v *viper.Viper) { 0, "Optional lines in the file. Normally, this is detected automatically but"+ " can be provided manually for performance, accuracy, or when using --local-file.") + flags.Int("line-additions", 0, "Optional number of lines added since last heartbeat in the current file.") + flags.Int("line-deletions", 0, "Optional number of lines deleted since last heartbeat in the current file.") flags.String( "local-file", "", diff --git a/cmd/run_test.go b/cmd/run_test.go index 10b66bac..4cc74a35 100644 --- a/cmd/run_test.go +++ b/cmd/run_test.go @@ -34,9 +34,13 @@ func TestRunCmd_Err(t *testing.T) { offlineQueueFile, err := os.CreateTemp(tmpDir, "") require.NoError(t, err) + defer offlineQueueFile.Close() + logFile, err := os.CreateTemp(tmpDir, "") require.NoError(t, err) + defer logFile.Close() + v := viper.New() v.Set("api-url", os.Getenv("TEST_SERVER_URL")) v.Set("entity", "/path/to/file") @@ -103,9 +107,13 @@ func TestRunCmd_Verbose_Err(t *testing.T) { offlineQueueFile, err := os.CreateTemp(tmpDir, "") require.NoError(t, err) + defer offlineQueueFile.Close() + logFile, err := os.CreateTemp(tmpDir, "") require.NoError(t, err) + defer logFile.Close() + v := viper.New() v.Set("api-url", os.Getenv("TEST_SERVER_URL")) v.Set("entity", "/path/to/file") @@ -172,9 +180,13 @@ func TestRunCmd_SendDiagnostics_Err(t *testing.T) { offlineQueueFile, err := os.CreateTemp(tmpDir, "") require.NoError(t, err) + defer offlineQueueFile.Close() + logFile, err := os.CreateTemp(tmpDir, "") require.NoError(t, err) + defer logFile.Close() + v := viper.New() v.Set("api-url", os.Getenv("TEST_SERVER_URL")) v.Set("entity", "/path/to/file") @@ -277,9 +289,13 @@ func TestRunCmd_SendDiagnostics_Panic(t *testing.T) { offlineQueueFile, err := os.CreateTemp(tmpDir, "") require.NoError(t, err) + defer offlineQueueFile.Close() + logFile, err := os.CreateTemp(tmpDir, "") require.NoError(t, err) + defer logFile.Close() + v := viper.New() v.Set("api-url", os.Getenv("TEST_SERVER_URL")) v.Set("entity", "/path/to/file") @@ -384,9 +400,13 @@ func TestRunCmd_SendDiagnostics_NoLogs_Panic(t *testing.T) { offlineQueueFile, err := os.CreateTemp(tmpDir, "") require.NoError(t, err) + defer offlineQueueFile.Close() + logFile, err := os.CreateTemp(tmpDir, "") require.NoError(t, err) + defer logFile.Close() + v := viper.New() v.Set("api-url", os.Getenv("TEST_SERVER_URL")) v.Set("entity", "/path/to/file") @@ -489,9 +509,13 @@ func TestRunCmd_SendDiagnostics_WakaError(t *testing.T) { offlineQueueFile, err := os.CreateTemp(tmpDir, "") require.NoError(t, err) + defer offlineQueueFile.Close() + logFile, err := os.CreateTemp(tmpDir, "") require.NoError(t, err) + defer logFile.Close() + v := viper.New() v.Set("api-url", os.Getenv("TEST_SERVER_URL")) v.Set("entity", "/path/to/file") diff --git a/go.mod b/go.mod index 63be975a..61ff59ba 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 - go.etcd.io/bbolt v1.3.7 + go.etcd.io/bbolt v1.3.8 golang.org/x/crypto v0.17.0 golang.org/x/net v0.15.0 golang.org/x/text v0.14.0 @@ -51,7 +51,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/yookoala/realpath v1.0.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index deaf8337..3aab83a3 100644 --- a/go.sum +++ b/go.sum @@ -279,6 +279,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= +go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -433,6 +435,8 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/main_test.go b/main_test.go index aeca0cd2..2fd3ad9b 100644 --- a/main_test.go +++ b/main_test.go @@ -129,6 +129,8 @@ func testSendHeartbeats(t *testing.T, projectFolder, entity, p string) { "--entity", entity, "--cursorpos", "12", "--offline-queue-file", offlineQueueFile.Name(), + "--line-additions", "123", + "--line-deletions", "456", "--lineno", "42", "--lines-in-file", "100", "--time", "1585598059", @@ -213,6 +215,8 @@ func TestSendHeartbeats_SecondaryApiKey(t *testing.T) { "--entity", "testdata/main.go", "--cursorpos", "12", "--offline-queue-file", offlineQueueFile.Name(), + "--line-additions", "123", + "--line-deletions", "456", "--lineno", "42", "--lines-in-file", "100", "--time", "1585598059", @@ -373,6 +377,8 @@ func TestSendHeartbeats_Err(t *testing.T) { "--entity", "testdata/main.go", "--cursorpos", "12", "--offline-queue-file", offlineQueueFile.Name(), + "--line-additions", "123", + "--line-deletions", "456", "--lineno", "42", "--lines-in-file", "100", "--time", "1585598059", diff --git a/pkg/heartbeat/heartbeat.go b/pkg/heartbeat/heartbeat.go index dba0a75d..17892717 100644 --- a/pkg/heartbeat/heartbeat.go +++ b/pkg/heartbeat/heartbeat.go @@ -31,6 +31,8 @@ type Heartbeat struct { IsWrite *bool `json:"is_write,omitempty"` Language *string `json:"language,omitempty"` LanguageAlternate string `json:"-"` + LineAdditions *int `json:"line_additions,omitempty"` + LineDeletions *int `json:"line_deletions,omitempty"` LineNumber *int `json:"lineno,omitempty"` Lines *int `json:"lines,omitempty"` LocalFile string `json:"-"` @@ -58,6 +60,8 @@ func New( isWrite *bool, language *string, languageAlternate string, + lineAdditions *int, + lineDeletions *int, lineNumber *int, lines *int, localFile string, @@ -78,6 +82,8 @@ func New( IsWrite: isWrite, Language: language, LanguageAlternate: languageAlternate, + LineAdditions: lineAdditions, + LineDeletions: lineDeletions, LineNumber: lineNumber, Lines: lines, LocalFile: localFile, diff --git a/pkg/heartbeat/heartbeat_test.go b/pkg/heartbeat/heartbeat_test.go index 37c2235b..c987d1f3 100644 --- a/pkg/heartbeat/heartbeat_test.go +++ b/pkg/heartbeat/heartbeat_test.go @@ -28,6 +28,8 @@ func TestNew(t *testing.T) { heartbeat.PointerTo(true), heartbeat.PointerTo("Go"), "Golang", + heartbeat.PointerTo(2), + heartbeat.PointerTo(3), heartbeat.PointerTo(42), nil, "/path/to/file", @@ -50,6 +52,8 @@ func TestNew(t *testing.T) { IsWrite: heartbeat.PointerTo(true), Language: heartbeat.PointerTo("Go"), LanguageAlternate: "Golang", + LineAdditions: heartbeat.PointerTo(2), + LineDeletions: heartbeat.PointerTo(3), LineNumber: heartbeat.PointerTo(42), LocalFile: "/path/to/file", ProjectAlternate: "billing", @@ -94,6 +98,8 @@ func TestHeartbeat_JSON(t *testing.T) { EntityType: heartbeat.FileType, IsWrite: heartbeat.PointerTo(true), Language: heartbeat.PointerTo("Go"), + LineAdditions: heartbeat.PointerTo(123), + LineDeletions: heartbeat.PointerTo(456), LineNumber: heartbeat.PointerTo(42), Lines: heartbeat.PointerTo(100), Project: heartbeat.PointerTo("wakatime"), diff --git a/pkg/heartbeat/testdata/heartbeat.json b/pkg/heartbeat/testdata/heartbeat.json index 56a6c0b2..1486420f 100644 --- a/pkg/heartbeat/testdata/heartbeat.json +++ b/pkg/heartbeat/testdata/heartbeat.json @@ -1,15 +1,17 @@ { - "branch": "heartbeat", - "category": "coding", - "cursorpos": 12, - "dependencies": ["dep1", "dep2"], - "entity": "/tmp/main.go", - "is_write": true, - "language": "Go", - "lineno": 42, - "lines": 100, - "project": "wakatime", - "type": "file", - "time": 1585598060.1, - "user_agent": "wakatime/13.0.7" + "branch": "heartbeat", + "category": "coding", + "cursorpos": 12, + "dependencies": ["dep1", "dep2"], + "entity": "/tmp/main.go", + "is_write": true, + "language": "Go", + "lineno": 42, + "lines": 100, + "line_additions": 123, + "line_deletions": 456, + "project": "wakatime", + "type": "file", + "time": 1585598060.1, + "user_agent": "wakatime/13.0.7" } diff --git a/testdata/api_heartbeats_request_template.json b/testdata/api_heartbeats_request_template.json index 59119e3f..fa2a8920 100644 --- a/testdata/api_heartbeats_request_template.json +++ b/testdata/api_heartbeats_request_template.json @@ -6,6 +6,8 @@ "entity": "%s", "is_write": true, "language": "Go", + "line_additions": 123, + "line_deletions": 456, "lineno": 42, "lines": 100, "project": "%s", diff --git a/testdata/extra_heartbeats.json b/testdata/extra_heartbeats.json index f1b2b1e7..af299b03 100644 --- a/testdata/extra_heartbeats.json +++ b/testdata/extra_heartbeats.json @@ -1 +1 @@ -[{ "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "vscode-wakatime", "time": 1585598059 }] +[{ "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "line_additions": 123, "line_deletions": 456, "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "wakatime-cli", "time": 1585598059 }, { "alternate_language": "Golang", "alternate_project": "billing", "category": "coding", "cursorpos": 12, "entity": "testdata/main.go", "entity_type": "file", "is_write": true, "language": "Go", "lineno": 42, "lines": 45, "project": "vscode-wakatime", "time": 1585598059 }]