diff --git a/src/cm/supportedModes.ts b/src/cm/supportedModes.ts index 12febc9f9..f16746a74 100644 --- a/src/cm/supportedModes.ts +++ b/src/cm/supportedModes.ts @@ -152,6 +152,27 @@ addMode( () => [], ); +// Languages that @codemirror/language-data only covers with legacy stream +// tokenizers and that the official Additional Language Modes plugin +// (acode.additional.langmodes) provides as Lezer grammars. They are left out +// so the plugin registers them under their canonical names; without it, these +// files fall back to plain text and Acode recommends the plugin. +const PLUGIN_PROVIDED_LANGUAGES = new Set([ + "clojure", + "clojurescript", + "edn", + "gas", + "kotlin", + "latex", + "stex", + "lua", + "swift", +]); + +// Extensions other modes claim in @codemirror/language-data but that the +// plugin has dedicated modes for: HTML lists Handlebars templates. +const PLUGIN_PROVIDED_EXTENSIONS = new Set(["hbs", "handlebars"]); + // 2) Register all languages provided by @codemirror/language-data // We convert extensions like [".js", ".mjs"] into a modelist pattern: "js|mjs" // and preserve aliases and filename regexes for languages like C++ and Dockerfile. @@ -159,6 +180,7 @@ for (const lang of languages as readonly LanguageDescription[]) { try { const name = String(lang?.name || "").trim(); if (!name) continue; + if (PLUGIN_PROVIDED_LANGUAGES.has(normalizeModeKey(name))) continue; const aliases = collectAliases(name, lang.alias); const modeId = getModeId(name, aliases); @@ -248,7 +270,9 @@ for (const lang of languages as readonly LanguageDescription[]) { for (const e of langExtensions) { if (typeof e !== "string") continue; const cleaned = e.replace(/^\./, "").trim(); - if (cleaned) parts.push(cleaned); + if (cleaned && !PLUGIN_PROVIDED_EXTENSIONS.has(cleaned.toLowerCase())) { + parts.push(cleaned); + } } const filenameMatchers: RegExp[] = []; diff --git a/tests/unit/pluginProvidedModes.test.js b/tests/unit/pluginProvidedModes.test.js new file mode 100644 index 000000000..959bf4897 --- /dev/null +++ b/tests/unit/pluginProvidedModes.test.js @@ -0,0 +1,104 @@ +import { afterEach, describe, expect, it } from "vitest"; +import "cm/supportedModes"; +import { addMode, getMode, getModeForPath, removeMode } from "cm/modelist"; + +// Modes the Additional Language Modes plugin (acode.additional.langmodes) +// replaces with Lezer grammars, with the extensions it registers. +const pluginModes = [ + { name: "assembly", extensions: ["asm", "s", "S", "nasm"] }, + { name: "c", extensions: ["c", "h", "ino"] }, + { name: "clojure", extensions: ["clj", "cljs", "cljc", "cljx", "edn"] }, + { name: "kotlin", extensions: ["kt", "kts"] }, + { name: "latex", extensions: ["tex", "ltx", "sty", "cls", "latex"] }, + { name: "lua", extensions: ["lua"] }, + { name: "swift", extensions: ["swift"] }, + { name: "handlebars", extensions: ["hbs", "handlebars"] }, + { name: "mustache", extensions: ["mustache"] }, +]; + +/** The plugin's registration rule: keep Acode's mode, add "-enhanced". */ +function registerLikePlugin({ name, extensions }) { + const registered = getMode(name) ? `${name}-enhanced` : name; + addMode(registered, extensions, registered, () => []); + return registered; +} + +describe("legacy modes replaced by the Additional Language Modes plugin", () => { + const registered = []; + + afterEach(() => { + for (const name of registered.splice(0)) removeMode(name); + }); + + it.each([ + "clojure", + "clojurescript", + "edn", + "gas", + "kotlin", + "latex", + "tex", + "stex", + "lua", + "swift", + ])("does not ship a built-in %s mode", (name) => { + expect(getMode(name)).toBeNull(); + }); + + it.each([ + "a.kt", + "a.kts", + "a.lua", + "a.swift", + "a.clj", + "a.cljs", + "a.edn", + "a.s", + "a.tex", + "a.hbs", + "a.handlebars", + ])("falls back to plain text for %s without the plugin", (path) => { + expect(getModeForPath(path).name).toBe("text"); + }); + + it("keeps C, C++, HTML and Luau built in", () => { + expect(getModeForPath("a.html").name).toBe("html"); + expect(getModeForPath("a.c").name).toBe("c"); + expect(getModeForPath("a.cpp").name).toBe("cpp"); + expect(getModeForPath("a.luau").name).toBe("luau"); + }); + + it("opens .text files as plain text instead of LaTeX", () => { + expect(getModeForPath("notes.text").name).toBe("text"); + }); + + it("lets the plugin register the replaced modes under canonical names", () => { + for (const mode of pluginModes) registered.push(registerLikePlugin(mode)); + + expect(registered).toEqual([ + "assembly", + "c-enhanced", + "clojure", + "kotlin", + "latex", + "lua", + "swift", + "handlebars", + "mustache", + ]); + expect(getModeForPath("a.kt").name).toBe("kotlin"); + expect(getModeForPath("a.lua").name).toBe("lua"); + expect(getModeForPath("a.swift").name).toBe("swift"); + expect(getModeForPath("a.cljs").name).toBe("clojure"); + expect(getModeForPath("a.edn").name).toBe("clojure"); + expect(getModeForPath("a.s").name).toBe("assembly"); + expect(getModeForPath("a.tex").name).toBe("latex"); + expect(getModeForPath("a.c").name).toBe("c-enhanced"); + expect(getModeForPath("a.hbs").name).toBe("handlebars"); + expect(getModeForPath("a.handlebars").name).toBe("handlebars"); + expect(getModeForPath("a.mustache").name).toBe("mustache"); + expect(getModeForPath("a.html").name).toBe("html"); + // Luau keeps its own mode even though "lua" now comes from the plugin. + expect(getModeForPath("a.luau").name).toBe("luau"); + }); +});