A lightweight, asynchronous Neovim plugin that runs norminette and displays errors directly in your editor as native diagnostics.
It also includes custom built-in checks for edge cases and subjective rules that the official norminette tool ignores, ensuring your C projects are fully compliant with the 42 Norm.
Docs: https://0xveya.github.io/dogshitnorm.nvim/
-
Asynchronous Execution: Runs
norminettein the background without freezing your editor. -
Native 42 Header Support: Inserts and refreshes the standard 42 header for C-like files and Makefiles without depending on
42Paris/42header. -
Header Styling: Merges the visual 42 header gradient/highlighting from
fancy-header.nviminto the main plugin with a toggle command. -
Header-Aware Viewing: Optionally fold the on-disk 42 header out of the way, remap
ggto the first real code line in that view, and display gutter line numbers relative to the first non-header line. -
Smart Header Guard Generator: Automatically inserts C-style
#ifndefguards in new.hfiles once the 42 header is present, ensures clean spacing, and keeps you in Normal mode. -
Makefile Boilerplate: Instantly populates new
Makefiles with 42-compliant mandatory rules (all,clean,fclean,re), dep-file support (-MMD -MP), and built-in debug toggles. -
42 Python Project UX:
:Pyprojectgeninitializes Python projects through your external setup CLI, with an interactivevim.uiflag picker (package name, line length, toolchain, checks, debug) and automatic test-suite copying. -
Smart Source Sync: Automatically detects your
SRC_DIRfrom the Makefile and syncs yourSRCSlist with all.cfiles found in that directory (recursive), while preserving sources assigned exclusively to secondary lists such asBONUS_SRCS. OnceNAMEis set, synchronization treats it as user-owned and never renames it. -
Makefile Ergonomics: Use
:Makelib [name]to convert the current project Makefile into a static-library template,:Makedebug [toggle|on|off]to toggle debug mode, and:Makestatusto show whether you are in library mode and whether debug/deps are enabled. -
Include Sorting: Sorts contiguous
#includeblocks alphabetically with:Includesort, or automatically before saving when enabled. -
Define Sorting: Sorts contiguous simple
# defineblocks alphabetically with:Definesort, while leaving header guard defines alone. -
Header Prototype Sorting: Sorts contiguous single-line function prototype blocks alphabetically with
:Protosort, or automatically before saving when enabled. -
Function Line Counts: Built-in Tree-sitter line count overlays for
.cfunctions, based on the oldft_count_lines.nvimbehavior. -
Line-Saving Actions: Functions get low-severity diagnostic hints with code actions for removable extra blank lines,
expr; return (value);->return (expr, value);, conservative while-counter increment rewrites, and aggressive folding of a one-linewhile (...)body increment into the condition. The norm-required blank line between declarations and code is kept. -
LSP Quick Fixes: Fixes are exposed through Neovim's LSP code-action UI, plus
:NormFixand:NormFixAll. This includes header guards, include/define/prototype sorting, Makefile source sync, whitespace cleanup, missingvoidparameters, missing return parentheses, missing blank lines between functions, and overlong-function line savers. -
Tree-sitter Extended Checks: Strict syntax-tree checks for rules
norminettemisses:- Type Naming: Enforces
s_,t_,u_, ande_prefixes. - 42 Header Validation: Ensures a valid header with student email and dates.
- Function Limits: Flags more than 5 functions per
.cfile, more than 4 parameters, more than 5 variables, missingvoidparameters, and unparenthesized return values. - Forbidden Syntax: Flags
for,do ... while,switch,case,goto, ternaries, and likely VLAs from Tree-sitter nodes. - Declaration Rules: Flags declarations after statements, multiple variables per declaration, inline initialization in functions, mutable globals, and bad identifier casing.
- Header Restrictions: Forbids
.cincludes, late includes, function bodies, and non-header syntax in headers. - Macro Restrictions: Flags lowercase macro names, multiline macros, and macros that contain code-like logic.
- Type Naming: Enforces
-
Makefile Strictness: Validates mandatory rules, ensures
allis the default target, and forbids wildcards (*.c). -
Native Diagnostics: Integrates with
vim.diagnosticfor virtual text and gutter signs. -
Directory Whitelisting: Only activates inside your specified 42 project folders.
Using lazy.nvim:
{
"0xveya/dogshitnorm.nvim",
ft = { "c", "cpp", "make" },
opts = {
-- Recommended: use 'uv' tool for a clean environment
cmd = { "uv", "tool", "run", "norminette" },
args = { "--no-colors" },
norminette_use_gitignore = true,
-- General Settings
keybinding = "<leader>cn",
lint_on_save = true,
-- Header Guard settings
auto_42_header = true,
update_42_header = true,
auto_header_guard = true,
header_keybinding = "<leader>42",
prototype_header_keybinding = "<leader>cp",
header_style_keybinding = "<leader>4h",
header_hide_enabled = false,
header_hide_keybinding = nil,
header_line_number_offset = false,
guard_keybinding = "<leader>ch",
auto_sort_defines = false,
auto_sort_prototypes = false,
line_count_enabled = false,
line_count_keybinding = "<leader>cl",
header_colors = {
box = { fg = "#6e6a86" },
filename = { fg = "#f6c177", bold = true },
author = { fg = "#9ccfd8" },
date = { fg = "#c4a7e7" },
logo_42 = { start = "#eb6f92", end_ = "#31748f" },
},
-- Makefile settings
auto_makefile = true,
makefile_keybinding = "<leader>cm",
project_type = "auto",
python_dirs = { "*python*", "*py*" },
c_dirs = {},
python_version = "3.10",
python_package = nil,
python_setup = {
script = "~/path/to/setup_project.py",
test_file = "~/path/to/test_cli_fw.py",
max_line_len = 100,
toolchain = "uv",
checks = { "mypy", "ruff", "pytest" },
},
norm_exclude_dirs = {},
-- Makefile Sync settings
auto_sync_makefile = true,
makesync_keybinding = "<leader>cu",
makefile_exclude_dirs = { ".git", ".jj", "tests", "test", "build", "libft", "libprintf" },
makefile_optional_libs = {
{
key = "libft",
dirs = { "libft" },
dir_var = "LIBFT_DIR",
lib_var = "LIBFT",
archive = "libft.a",
},
{
key = "printf",
dirs = { "ft_printf", "libprintf", "libftprintf" },
dir_var = "PRINTF_DIR",
lib_var = "PRINTF",
archives = {
ft_printf = "libftprintf.a",
libprintf = "libprintf.a",
libftprintf = "libftprintf.a",
},
},
},
-- Optional: Only run inside these directories
active_dirs = {
"~/coding/42",
},
},
}
- Linting: Save your file (
:w) or press<leader>cn. - 42 Header: Run
:Stdheaderor pressheader_keybindingto insert/refresh the native 42 header. Saving a file updates theUpdated:line when enabled. - Bulk Headers: Run
:StdheaderAll [path]to add or refresh 42 headers across a project tree in one shot. - Header Styling: Run
:HeaderToggleor pressheader_style_keybindingto toggle the mergedfancy-header.nvimvisuals. - Header Hiding: Run
:HeaderHide [toggle|on|off]or setheader_hide_enabled = trueto fold the real header out of the way while keeping it in the file for norminette. In that view,ggjumps to the first non-header line. - Header-Relative Numbers: Set
header_line_number_offset = trueto show gutter line numbers relative to the first non-header line instead of counting the 42 header. - Auto-Guards: Creating or saving a
.hfile inside an active directory will automatically trigger/fix inclusion guards. - Include Sorting: Run
:Includesortinside a.cor.hfile to sort contiguous include blocks. Setauto_sort_includes = trueto run this automatically before saving C and header files. - Define Sorting: Run
:Definesortinside a.cor.hfile to sort contiguous simple define blocks. Setauto_sort_defines = trueto run this automatically before saving C and header files. - Prototype Sorting: Run
:Protosortinside a.hfile to sort contiguous function prototype blocks. Setauto_sort_prototypes = trueto run this automatically before saving headers. - Prototype Export: Put the cursor inside a
.cfunction or visually select it, then run:Protoheaderor pressprototype_header_keybindingto insert its single-line prototype into the resolved header file and save that header. - Line Counts: Run
:NormLineCountsor pressline_count_keybindingto toggle virtual line count overlays above.cfunctions. - Line Savers: Run
:NormLineSaversor use LSP code actions inside a.cfunction. Actions can remove extra blank lines inside the current function, combine an expression directly followed by a return with the comma operator, move simples++/s--loop increments into a nearby*sexpression, or fold a single-linewhile (...)body increment intowhile ((...) && (s++, 1));. The required declaration/code separator blank line is not offered as removable. More complex while-loop counter patterns are still listed as suggestions instead of being applied blindly. - Quick Fixes: Use Neovim's
vim.lsp.buf.code_action()(graby default on current Neovim) on a diagnostic line, run:NormFix, or run:NormFixAllto repair safe file-level issues such as header guards, include/define/prototype order, Makefile sources, whitespace, return parentheses, missingvoid, function spacing, overlong-function line savers, and snake_case naming. - Auto-Makefile: Creating a new (empty)
Makefilewill trigger the 42 Header and append a project stub whoseNAMEis inferred from the project directory. The stub builds in parallel by default (JOBS ?= $(shell nproc)), keeps objects and dep files inobj/, and makes objects depend on the Makefile so editing it triggers rebuilds. If configured optional libraries such aslibftorft_printf/libprintfexist in the project root, the generated Makefile also gets recursive build/clean rules and links those archives automatically. If none are found, the stub leaves a comment saying so. Once a Makefile has a body, opening it never re-templates or rewrites it. - Python Init: Python projects are initialized through an external setup CLI configured via
python_setup.script.:Pyprojectgen(or:Makegen pythonon an empty Makefile) collects the flags (package name, line length, toolchain, checks, debug) throughvim.uiprompts, runs the CLI, copies the configured test suite intotests/with rewritten imports, and writes.gitignore/.editorconfig/.python-versionwhen missing. It refuses to run when a non-emptypyproject.tomlexists. - Python Isolation: Python files and Python-project Makefiles do not get 42 headers and do not run norminette. C-only Makefile commands notify instead of rewriting Python Makefiles.
norm_exclude_dirssuppresses norm diagnostics for whole directories. - Source Sync: Press
<leader>cu(or run:Makesync) inside a Makefile. The plugin will read yourSRC_DIRvariable, crawl that folder for.cfiles, update yourSRCSblock with proper 42-style formatting and backslashes, and write the Makefile. Files listed only in a secondary source variable such asBONUS_SRCSstay out of mandatorySRCS. - Library Conversion: Run
:Makelibto rewrite the current Makefile as a static-library build. Pass an optional archive name such as:Makelib libftprintfor:Makelib libftprintf.a. IfNAMEalready ends in.a, that archive name is preserved and:Makestatusreports library mode automatically. - Debug Toggle: Run
:Makedebug,:Makedebug on, or:Makedebug offto flip the MakefileDEBUGflag. The plugin will ensure the dep-file and debug boilerplate exists and then notify the current state. - Makefile Status: Run
:Makestatusto see the current target name, whether the file is in library or binary mode, and whether debug/dependency tracking is enabled.
| Option | Type | Default | Description |
|---|---|---|---|
cmd |
table |
{"norminette"} |
The command to execute. |
norminette_format |
string |
"json" |
Ask norminette for structured JSON output first. Falls back to human output parsing only if JSON is unavailable. |
norminette_use_gitignore |
boolean |
true |
Pass --use-gitignore by default unless you already included it in args. |
suppress_duplicate_diagnostics |
boolean |
true |
Hide Tree-sitter diagnostics when norminette already reported the same rule. |
lsp_code_actions |
boolean |
true |
Start an in-process dogshitnorm LSP client that contributes code actions to vim.lsp.buf.code_action(). |
auto_42_header |
boolean |
true |
Insert the standard 42 header automatically in new C-like files and Makefiles. |
update_42_header |
boolean |
true |
Refresh the Updated: timestamp on save when a 42 header is present. |
auto_header_guard |
boolean |
true |
Auto-insert guards in .h files. |
header_keybinding |
string |
"<leader>42" |
Keymap to insert or refresh the 42 header. |
prototype_header_keybinding |
string |
"<leader>cp" |
Normal/visual keymap to send a function prototype into the resolved header file. |
header_style_enabled |
boolean |
true |
Apply merged fancy-header.nvim visual styling to 42 headers. |
header_style_keybinding |
string |
"<leader>4h" |
Keymap to toggle header styling. |
header_hide_enabled |
boolean |
false |
Fold the 42 header out of the active window while keeping it in the file. |
header_hide_keybinding |
string |
nil |
Optional keymap to toggle the hidden-header view. |
header_line_number_offset |
boolean |
false |
Display line numbers relative to the first non-header line when a 42 header is present. |
header_user |
string |
nil |
Override the 42 username used in generated headers. Falls back to vim.g.user42. |
header_email |
string |
nil |
Override the 42 email used in generated headers. Falls back to vim.g.mail42. |
header_colors |
table |
Rosé Pine-style defaults | Highlight groups and gradient colors for the visual 42 header layer. |
auto_sort_includes |
boolean |
false |
Auto-sort contiguous #include blocks before saving .c and .h files. |
auto_sort_defines |
boolean |
false |
Auto-sort contiguous simple # define blocks before saving .c and .h files. |
auto_sort_prototypes |
boolean |
false |
Auto-sort contiguous function prototype blocks before saving .h files. |
line_count_enabled |
boolean |
false |
Show Tree-sitter function line count overlays automatically. |
line_count_keybinding |
string |
"<leader>cl" |
Keymap to toggle function line counts. |
line_count_limit |
number |
25 |
Highlight function line counts above this limit as errors. |
line_count_formatter |
function |
nil |
Optional formatter called with (count, limit) for line count overlays. |
line_saver_diagnostics |
boolean |
true |
Emit hint diagnostics for safe line-saving opportunities. |
line_saver_always |
boolean |
true |
Emit line-saver hint diagnostics even before a function exceeds the line limit. |
line_saver_max_width |
number |
80 |
Maximum generated line width for comma-expression return fixes. |
debug_log |
boolean |
false |
Write dogshitnorm troubleshooting logs when enabled. |
debug_log_path |
string |
nil |
Log file path. Defaults to stdpath("cache") .. "/dogshitnorm.log". |
auto_makefile |
boolean |
true |
Auto-populate new Makefiles. |
auto_sync_makefile |
boolean |
true |
Enable the :Makesync command. |
keybinding |
string |
"<leader>cn" |
Keymap to trigger linting. |
fix_keybinding |
string |
nil |
Optional keymap to trigger :NormFix-style fixes. |
guard_keybinding |
string |
"<leader>ch" |
Keymap to trigger header guard. |
makefile_keybinding |
string |
"<leader>cm" |
Keymap to trigger Makefile stub. |
project_type |
string |
"auto" |
"auto", "c", or "python" project selection for :Makegen. |
python_dirs |
table |
{"*python*","*py*"} |
Case-insensitive folder globs that auto-detect Python projects. |
c_dirs |
table |
{} |
Case-insensitive folder globs that force C during auto-detection. |
python_version |
string |
"3.10" |
Version written to .python-version. Override this per subject, e.g. "3.12". |
python_package |
string |
nil |
Package name; inferred from folder name when unset. Set this when folder and import package differ. |
python_setup |
table |
{ script = nil, ... } |
External Python setup CLI: script (required), python, test_file, and prompt defaults max_line_len, toolchain, checks, debug. |
norm_exclude_dirs |
table |
{} |
Directories where norm diagnostics are suppressed entirely. |
makesync_keybinding |
string |
"<leader>cu" |
Keymap to sync SRCS with SRC_DIR. |
makefile_exclude_dirs |
table |
{".git",".jj","tests","test","build","libft","libprintf"} |
Directories ignored when syncing Makefile sources. Any path segment containing tester is also ignored. |
makefile_optional_libs |
table |
default libft / ft_printf / libprintf mapping |
Optional libraries to auto-detect in generated Makefiles. Each entry can define dirs, dir_var, lib_var, archive, and per-directory archives. |
active_dirs |
table |
nil |
List of allowed project paths. |
- Neovim 0.10+
- Tree-sitter C parser: Required for the extended
.c/.hchecks. - Norminette: Installed and accessible in your path.