1package mode
2
3import (
4	"path/filepath"
5	"strconv"
6	"strings"
7)
8
9// Detect looks at the filename and tries to guess what could be an appropriate editor mode.
10func Detect(filename string) Mode {
11
12	// A list of the most common configuration filenames that does not have an extension
13	var (
14		configFilenames = []string{"fstab", "config", "BUILD", "WORKSPACE", "passwd", "group", "environment", "shadow", "gshadow", "hostname", "hosts", "issue", "mirrorlist"}
15		mode            Mode
16	)
17
18	baseFilename := filepath.Base(filename)
19	ext := filepath.Ext(baseFilename)
20
21	// Check if we should be in a particular mode for a particular type of file
22	switch {
23	case baseFilename == "COMMIT_EDITMSG" ||
24		baseFilename == "MERGE_MSG" ||
25		(strings.HasPrefix(baseFilename, "git-") &&
26			!strings.Contains(baseFilename, ".") &&
27			strings.Count(baseFilename, "-") >= 2):
28		// Git mode
29		mode = Git
30	case ext == ".vimrc" || ext == ".vim" || ext == ".nvim":
31		mode = Vim
32	case strings.HasPrefix(baseFilename, "Makefile") || strings.HasPrefix(baseFilename, "makefile") || baseFilename == "GNUmakefile":
33		// NOTE: This one MUST come before the ext == "" check below!
34		mode = Makefile
35	case strings.HasSuffix(filename, ".git/config") || ext == ".ini" || ext == ".cfg" || ext == ".conf" || ext == ".service" || ext == ".target" || ext == ".socket" || strings.HasPrefix(ext, "rc"):
36		fallthrough
37	case ext == ".yml" || ext == ".toml" || ext == ".ini" || ext == ".bp" || ext == ".rule" || strings.HasSuffix(filename, ".git/config") || (ext == "" && (strings.HasSuffix(baseFilename, "file") || strings.HasSuffix(baseFilename, "rc") || hasS(configFilenames, baseFilename))):
38		mode = Config
39	case ext == ".sh" || ext == ".ksh" || ext == ".tcsh" || ext == ".bash" || ext == ".zsh" || ext == ".local" || ext == ".profile" || baseFilename == "PKGBUILD" || (strings.HasPrefix(baseFilename, ".") && strings.Contains(baseFilename, "sh")): // This last part covers .bashrc, .zshrc etc
40		mode = Shell
41	case ext == ".bzl" || baseFilename == "BUILD" || baseFilename == "WORKSPACE":
42		mode = Bazel
43	case baseFilename == "CMakeLists.txt" || ext == ".cmake":
44		mode = CMake
45	default:
46		switch ext {
47		case ".s", ".S", ".asm", ".inc":
48			// Go-style assembly (modeGoAssembly) is enabled if a mid-dot is discovered
49			mode = Assembly
50		//case ".s":
51		//mode = GoAssembly
52		case ".amber":
53			mode = Amber
54		case ".go":
55			mode = Go
56		case ".odin":
57			mode = Odin
58		case ".hs":
59			mode = Haskell
60		case ".sml":
61			mode = StandardML
62		case ".m4":
63			mode = M4
64		case ".ml":
65			mode = OCaml // or standard ML, if the file does not contain ";;"
66		case ".py":
67			mode = Python
68		case ".pl":
69			mode = Perl
70		case ".md":
71			// Markdown mode
72			mode = Markdown
73		case ".bts":
74			mode = Battlestar
75		case ".cpp", ".cc", ".c++", ".cxx", ".hpp", ".h":
76			// C++ mode
77			// TODO: Find a way to discover is a .h file is most likely to be C or C++
78			mode = Cpp
79		case ".c":
80			// C mode
81			mode = C
82		case ".d":
83			// D mode
84			mode = D
85		case ".cs":
86			// C# mode
87			mode = CS
88		case ".adoc", ".rst", ".scdoc", ".scd":
89			// Markdown-like syntax highlighting
90			// TODO: Introduce a separate mode for these.
91			mode = Markdown
92		case ".txt", ".text", ".nfo", ".diz":
93			mode = Text
94		case ".clj", ".clojure", "cljs":
95			mode = Clojure
96		case ".lsp", ".emacs", ".el", ".elisp", ".lisp", ".cl", ".l":
97			mode = Lisp
98		case ".zig", ".zir":
99			mode = Zig
100		case ".v":
101			mode = V
102		case ".kt", ".kts":
103			mode = Kotlin
104		case ".java":
105			mode = Java
106		case ".gradle":
107			mode = Gradle
108		case ".hal":
109			mode = HIDL
110		case ".aidl":
111			mode = AIDL
112		case ".sql":
113			mode = SQL
114		case ".ok":
115			mode = Oak
116		case ".rs":
117			mode = Rust
118		case ".lua":
119			mode = Lua
120		case ".cr":
121			mode = Crystal
122		case ".nim":
123			mode = Nim
124		case ".pas", ".pp", ".lpr":
125			mode = ObjectPascal
126		case ".bat":
127			mode = Bat
128		case ".adb", ".gpr", ".ads", ".ada":
129			mode = Ada
130		case ".htm", ".html":
131			mode = HTML
132		case ".xml":
133			mode = XML
134		case ".te":
135			mode = PolicyLanguage
136		case ".1", ".2", ".3", ".4", ".5", ".6", ".7", ".8":
137			mode = Nroff
138		case ".scala":
139			mode = Scala
140		case ".json", ".ipynb":
141			mode = JSON
142		case ".js":
143			mode = JavaScript
144		case ".ts":
145			mode = TypeScript
146		default:
147			mode = Blank
148		}
149	}
150
151	if mode == Text {
152		mode = Markdown
153	}
154
155	// If the mode is not set, and there is no extensions
156	if mode == Blank && !strings.Contains(baseFilename, ".") {
157		if baseFilename == strings.ToUpper(baseFilename) {
158			// If the filename is all uppercase and no ".", use mode.Markdown
159			mode = Markdown
160		} else if len(baseFilename) > 2 && baseFilename[2] == '-' {
161			// Could it be a rule-file, that starts with ie. "90-" ?
162			if _, err := strconv.Atoi(baseFilename[:2]); err == nil { // success
163				// Yes, assume this is a shell-like configuration file
164				mode = Config
165			}
166		}
167	}
168
169	return mode
170}
171