Skip to content

Commit ab3d4e7

Browse files
authored
Merge pull request #1171 from wakatime/bugfix/forth-language
Detect Forth language from file contents
2 parents a76bb39 + 0197c4b commit ab3d4e7

File tree

4 files changed

+111
-6
lines changed

4 files changed

+111
-6
lines changed

pkg/language/forth.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package language
2+
3+
import (
4+
"regexp"
5+
"strings"
6+
7+
"github.com/wakatime/wakatime-cli/pkg/heartbeat"
8+
)
9+
10+
var forthFuncTest = regexp.MustCompile(`:[^\n\r]+;[\n\r]`)
11+
12+
// detectForthFromContents tries to detect the language from the file contents.
13+
func detectForthFromContents(text string) (heartbeat.Language, float32, bool) {
14+
var weight float32
15+
16+
if forthFuncTest.MatchString(text) {
17+
weight = 0.9
18+
}
19+
20+
if strings.Contains(text, "\\ ") {
21+
weight += 0.5
22+
}
23+
24+
if strings.Contains(text, "( ") {
25+
weight += 0.2
26+
}
27+
28+
if weight > 1 {
29+
weight = 1
30+
}
31+
32+
return heartbeat.LanguageUnknown, weight, weight > 0
33+
}

pkg/language/fsharp.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package language
2+
3+
import (
4+
"strings"
5+
6+
"github.com/wakatime/wakatime-cli/pkg/heartbeat"
7+
)
8+
9+
// detectFSharpFromContents tries to detect the language from the file contents.
10+
func detectFSharpFromContents(text string) (heartbeat.Language, float32, bool) {
11+
var weight float32
12+
13+
if strings.Contains(text, "let ") && strings.Contains(text, "match ") && strings.Contains(text, " ->") {
14+
weight = 0.9
15+
}
16+
17+
if strings.Contains(text, "// ") || strings.Contains(text, "(* ") && strings.Contains(text, " *)") {
18+
weight += 0.7
19+
}
20+
21+
if weight > 1 {
22+
weight = 1
23+
}
24+
25+
return heartbeat.LanguageUnknown, weight, weight > 0
26+
}

pkg/language/language.go

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package language
33
import (
44
"context"
55
"fmt"
6+
"io"
67
"os"
78
"path/filepath"
89
"strings"
910

11+
"github.com/wakatime/wakatime-cli/pkg/file"
1012
"github.com/wakatime/wakatime-cli/pkg/heartbeat"
1113
"github.com/wakatime/wakatime-cli/pkg/log"
1214
)
@@ -72,11 +74,7 @@ func Detect(ctx context.Context, fp string, guessLanguage bool) (heartbeat.Langu
7274
language = languageChroma
7375
}
7476

75-
languageVim, weightVim, okVim := detectVimModeline(fp)
76-
if okVim && weightVim > weight {
77-
// use language from vim modeline, if weight is higher
78-
language = languageVim
79-
}
77+
language = detectOverrideCases(ctx, fp, language, weight)
8078

8179
if language == heartbeat.LanguageUnknown {
8280
return heartbeat.LanguageUnknown, fmt.Errorf("could not detect the language of file %q", fp)
@@ -131,6 +129,54 @@ func detectSpecialCases(ctx context.Context, fp string) (heartbeat.Language, boo
131129
return heartbeat.LanguageUnknown, false
132130
}
133131

132+
// detectOverrideCases overwrides the Chroma detected language based on file contents.
133+
func detectOverrideCases(ctx context.Context, fp string, language heartbeat.Language, weight float32) heartbeat.Language {
134+
logger := log.Extract(ctx)
135+
136+
f, err := file.OpenNoLock(fp) // nolint:gosec
137+
if err != nil {
138+
logger.Debugf("failed to open file: %s", err)
139+
return language
140+
}
141+
142+
defer func() {
143+
if err := f.Close(); err != nil {
144+
logger.Debugf("failed to close file: %s", err)
145+
}
146+
}()
147+
148+
buf := make([]byte, 4096)
149+
c, err := f.Read(buf)
150+
if err != nil && err != io.EOF {
151+
logger.Debugf("failed to open file: %s", err)
152+
return language
153+
}
154+
155+
text := string(buf[:c])
156+
157+
languageVim, weightVim, okVim := detectVimModeline(text)
158+
if okVim && weightVim > weight {
159+
language = languageVim
160+
}
161+
162+
_, file := filepath.Split(fp)
163+
ext := strings.ToLower(filepath.Ext(file))
164+
165+
if ext == ".fs" {
166+
languageForth, weightForth, okForth := detectForthFromContents(text)
167+
if okForth && weightForth >= weight {
168+
language = languageForth
169+
}
170+
171+
languageFSharp, weightFSharp, okFSharp := detectFSharpFromContents(text)
172+
if okFSharp && weightFSharp >= weight {
173+
language = languageFSharp
174+
}
175+
}
176+
177+
return language
178+
}
179+
134180
// folderContainsCFiles returns true, if filder contains c files.
135181
func folderContainsCFiles(ctx context.Context, dir string) bool {
136182
if dir == "" {

pkg/language/vim_internal_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func TestDetectVimModeline(t *testing.T) {
2929
Language: heartbeat.LanguagePython,
3030
},
3131
"syntax": {
32-
Text: "/* vim: syntax=python tw=60 ts=2: */",
32+
Text: "/* vim: syntax=python tw=60 ts=2: */\n\nprint('hello world)",
3333
Language: heartbeat.LanguagePython,
3434
},
3535
"different order": {

0 commit comments

Comments
 (0)