From 0e97a4c38f75f78bf5919875e826d81d65b3b5b5 Mon Sep 17 00:00:00 2001 From: amandeavor Date: Fri, 25 Sep 2026 07:53:36 +0000 Subject: [PATCH] fix(tui): highlight Containerfile, Dockerfile.*, and shell dotfiles languageForPath missed Podman Containerfiles, Dockerfile.dev-style variants, GNUmakefile, Vagrantfile, and common shell rc files. Add exact-name mappings, a Dockerfile.*/Containerfile.* prefix rule, and dedicated highlight tests. Closes #427 --- packages/tui/src/highlight.ts | 12 +++++++ packages/tui/test/highlight.test.ts | 49 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 packages/tui/test/highlight.test.ts diff --git a/packages/tui/src/highlight.ts b/packages/tui/src/highlight.ts index e860b65f..39b49d01 100644 --- a/packages/tui/src/highlight.ts +++ b/packages/tui/src/highlight.ts @@ -55,11 +55,18 @@ const LANGUAGE_ALIASES: Readonly> = { const FILE_LANGUAGES: Readonly> = { dockerfile: "dockerfile", + containerfile: "dockerfile", makefile: "makefile", + gnumakefile: "makefile", cmakelists: "cmake", "cmakelists.txt": "cmake", gemfile: "ruby", rakefile: "ruby", + vagrantfile: "ruby", + ".bashrc": "bash", + ".zshrc": "bash", + ".bash_profile": "bash", + ".profile": "bash", }; function languageName(language: string | undefined): string | undefined { @@ -75,6 +82,11 @@ export function languageForPath(path: string): string | undefined { const name = path.split(/[\\/]/u).at(-1)?.toLowerCase() ?? ""; const named = FILE_LANGUAGES[name]; if (named !== undefined) return named; + // Dockerfile.* / Containerfile.* keep dockerfile highlighting even when the + // "extension" is an environment name (dev, prod, …) that is not a language. + if (name.startsWith("dockerfile.") || name.startsWith("containerfile.")) { + return "dockerfile"; + } const extension = name.includes(".") ? name.slice(name.lastIndexOf(".") + 1) : ""; return languageName(extension); } diff --git a/packages/tui/test/highlight.test.ts b/packages/tui/test/highlight.test.ts new file mode 100644 index 00000000..b9521453 --- /dev/null +++ b/packages/tui/test/highlight.test.ts @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2026 Kaushik Kumar +// SPDX-License-Identifier: Apache-2.0 + +import assert from "node:assert/strict"; +import test from "node:test"; +import { languageForPath } from "../src/highlight.ts"; + +test("languageForPath maps exact file names", () => { + const cases: Array<[string, string | undefined]> = [ + ["Dockerfile", "dockerfile"], + ["Containerfile", "dockerfile"], + ["Makefile", "makefile"], + ["GNUmakefile", "makefile"], + ["CMakeLists.txt", "cmake"], + ["Gemfile", "ruby"], + ["Rakefile", "ruby"], + ["Vagrantfile", "ruby"], + [".bashrc", "bash"], + [".zshrc", "bash"], + [".bash_profile", "bash"], + [".profile", "bash"], + ]; + for (const [path, expected] of cases) { + assert.equal(languageForPath(path), expected, path); + } +}); + +test("languageForPath maps Dockerfile.* and Containerfile.* prefixes", () => { + assert.equal(languageForPath("Dockerfile.dev"), "dockerfile"); + assert.equal(languageForPath("Dockerfile.prod"), "dockerfile"); + assert.equal(languageForPath("Containerfile.local"), "dockerfile"); +}); + +test("languageForPath maps common extensions", () => { + assert.equal(languageForPath("src/app.ts"), "typescript"); + assert.equal(languageForPath("main.py"), "python"); + assert.equal(languageForPath("lib.rs"), "rust"); +}); + +test("languageForPath is case-insensitive and accepts Windows paths", () => { + assert.equal(languageForPath("C:\\repo\\Dockerfile"), "dockerfile"); + assert.equal(languageForPath("C:\\repo\\Containerfile"), "dockerfile"); + assert.equal(languageForPath("packages\\TUI\\App.TS"), "typescript"); +}); + +test("languageForPath returns undefined for unknown names", () => { + assert.equal(languageForPath("notes"), undefined); + assert.equal(languageForPath("archive.zzz"), undefined); +});