untested, just committing to save everything.

This commit is contained in:
Norm Rasmussen
2026-04-14 20:20:41 -04:00
commit 578e71442e
9 changed files with 1210 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

178
README.md Normal file
View File

@ -0,0 +1,178 @@
# n8n.nvim
A dark Neovim colorscheme inspired by the [n8n](https://n8n.io) workflow automation platform's dark mode UI.
Built from n8n's actual CSS design tokens: the same neutrals, the signature orange accent, the purple/blue/green syntax colors you see in the n8n editor and JSON viewer.
## Features
- Full Treesitter highlight group support
- LSP semantic token highlights
- Terminal colors
- Lualine theme included
- Plugin support: Telescope, NvimTree, Neo-tree, cmp, Blink.cmp, GitSigns, Indent Blankline, Which-Key, Lazy, Mason, Noice, Notify, Trouble, Mini, Bufferline, Snacks, and more
- Transparent background option
- Customizable styles and color overrides
## Installation
### vim.pack (native packages)
Clone the repo into your Neovim pack path so it loads automatically:
```bash
git clone https://github.com/your-username/n8n.nvim \
~/.config/nvim/pack/plugins/start/n8n.nvim
```
Then in your `init.lua`:
```lua
require("n8n").setup({
-- optional config
})
vim.cmd("colorscheme n8n")
```
If you prefer to load it on demand, clone into `opt` instead:
```bash
git clone https://github.com/your-username/n8n.nvim \
~/.config/nvim/pack/plugins/opt/n8n.nvim
```
```lua
vim.cmd("packadd n8n.nvim")
require("n8n").setup()
vim.cmd("colorscheme n8n")
```
### lazy.nvim
```lua
{
"your-username/n8n.nvim",
lazy = false,
priority = 1000,
config = function()
require("n8n").setup({
-- your config here (optional)
})
vim.cmd("colorscheme n8n")
end,
}
```
### packer.nvim
```lua
use({
"your-username/n8n.nvim",
config = function()
require("n8n").setup()
vim.cmd("colorscheme n8n")
end,
})
```
### vim-plug
```vim
Plug 'your-username/n8n.nvim'
" in your init.vim, after plug#end():
lua require('n8n').setup()
colorscheme n8n
```
## Configuration
Call `setup()` **before** setting the colorscheme. All options are optional.
```lua
require("n8n").setup({
transparent = false, -- enable transparent background
terminal_colors = true, -- configure terminal colors
dim_inactive = false, -- dim inactive split windows
styles = {
comments = { italic = true },
keywords = {},
functions = {},
strings = {},
variables = {},
},
-- Override specific colors
on_colors = function(colors)
-- colors.bg = "#000000"
return colors
end,
-- Override specific highlight groups
on_highlights = function(highlights, colors)
-- highlights.Normal = { fg = "#ffffff", bg = "#000000" }
end,
})
```
## Lualine
The theme ships with a built-in lualine theme:
```lua
require("lualine").setup({
options = {
theme = "n8n",
},
})
```
## Palette
The color palette is extracted directly from n8n's dark mode CSS custom properties. Key colors:
| Role | Color | Hex |
|------|-------|-----|
| Background | ![#171717](https://placehold.co/15x15/171717/171717.png) | `#171717` |
| Surface | ![#212121](https://placehold.co/15x15/212121/212121.png) | `#212121` |
| Foreground | ![#e0e0e0](https://placehold.co/15x15/e0e0e0/e0e0e0.png) | `#e0e0e0` |
| Accent (Orange) | ![#ff6f5c](https://placehold.co/15x15/ff6f5c/ff6f5c.png) | `#ff6f5c` |
| Keywords | ![#ff6f5c](https://placehold.co/15x15/ff6f5c/ff6f5c.png) | `#ff6f5c` |
| Strings | ![#a098dc](https://placehold.co/15x15/a098dc/a098dc.png) | `#a098dc` |
| Functions | ![#7fb3e6](https://placehold.co/15x15/7fb3e6/7fb3e6.png) | `#7fb3e6` |
| Types | ![#c3bee9](https://placehold.co/15x15/c3bee9/c3bee9.png) | `#c3bee9` |
| Numbers | ![#29a360](https://placehold.co/15x15/29a360/29a360.png) | `#29a360` |
| Constants | ![#e6a23d](https://placehold.co/15x15/e6a23d/e6a23d.png) | `#e6a23d` |
| Comments | ![#828282](https://placehold.co/15x15/828282/828282.png) | `#828282` |
| Error | ![#ea1f30](https://placehold.co/15x15/ea1f30/ea1f30.png) | `#ea1f30` |
| Warning | ![#ffc400](https://placehold.co/15x15/ffc400/ffc400.png) | `#ffc400` |
| Info | ![#2878c8](https://placehold.co/15x15/2878c8/2878c8.png) | `#2878c8` |
| Hint | ![#33cc78](https://placehold.co/15x15/33cc78/33cc78.png) | `#33cc78` |
## Project Structure
```
n8n.nvim/
├── colors/
│ └── n8n.lua # entry point for :colorscheme n8n
├── lua/
│ ├── n8n/
│ │ ├── init.lua # setup() and load() logic
│ │ ├── palette.lua # raw hex values from n8n's CSS
│ │ ├── theme.lua # semantic color mapping
│ │ └── groups.lua # all highlight group definitions
│ └── lualine/
│ └── themes/
│ └── n8n.lua # lualine theme extra
├── LICENSE
└── README.md
```
## Acknowledgements
Colors derived from the [n8n design system](https://github.com/n8n-io/n8n). This is a community project and is not affiliated with n8n GmbH.
## License
MIT

556
groups.lua Normal file
View File

@ -0,0 +1,556 @@
local theme = require("n8n.theme")
local M = {}
---@param opts? table
function M.setup(opts)
opts = opts or {}
local c = theme.colors()
local groups = {}
-- ============================================================
-- Core editor highlights
-- ============================================================
groups.Normal = { fg = c.fg, bg = c.bg }
groups.NormalFloat = { fg = c.fg, bg = c.bg_float }
groups.NormalSB = { fg = c.fg_sidebar, bg = c.bg_sidebar }
groups.SignColumn = { fg = c.fg_gutter, bg = c.bg }
groups.FoldColumn = { fg = c.fg_gutter, bg = c.bg }
groups.MsgArea = { fg = c.fg }
groups.MsgSeparator = { fg = c.border }
groups.SpellBad = { sp = c.error, undercurl = true }
groups.SpellCap = { sp = c.warning, undercurl = true }
groups.SpellLocal = { sp = c.info, undercurl = true }
groups.SpellRare = { sp = c.hint, undercurl = true }
-- Cursor and selections
groups.Cursor = { fg = c.bg, bg = c.fg_bright }
groups.lCursor = { link = "Cursor" }
groups.CursorIM = { link = "Cursor" }
groups.TermCursor = { fg = c.bg, bg = c.accent }
groups.TermCursorNC = { fg = c.bg, bg = c.fg_dark }
groups.CursorLine = { bg = c.bg_highlight }
groups.CursorColumn = { bg = c.bg_highlight }
groups.ColorColumn = { bg = c.bg_highlight }
groups.Visual = { bg = c.bg_visual }
groups.VisualNOS = { bg = c.bg_visual }
-- Line numbers
groups.LineNr = { fg = c.fg_gutter }
groups.CursorLineNr = { fg = c.accent, bold = true }
groups.LineNrAbove = { fg = c.fg_gutter }
groups.LineNrBelow = { fg = c.fg_gutter }
-- Search
groups.Search = { fg = c.fg_bright, bg = c.bg_search }
groups.IncSearch = { fg = c.bg, bg = c.accent }
groups.CurSearch = { link = "IncSearch" }
groups.Substitute = { fg = c.bg, bg = c.accent }
-- Popup menu
groups.Pmenu = { fg = c.fg, bg = c.bg_popup }
groups.PmenuSel = { fg = c.fg_bright, bg = c.bg_visual }
groups.PmenuSbar = { bg = c.bg_popup }
groups.PmenuThumb = { bg = c.border }
groups.PmenuKind = { fg = c.accent }
groups.PmenuKindSel = { fg = c.accent, bg = c.bg_visual }
groups.PmenuExtra = { fg = c.fg_dark }
groups.PmenuExtraSel = { fg = c.fg_dark, bg = c.bg_visual }
-- Tabline
groups.TabLine = { fg = c.fg_dark, bg = c.bg_statusline }
groups.TabLineFill = { bg = c.bg_dark }
groups.TabLineSel = { fg = c.fg_bright, bg = c.bg }
-- Statusline
groups.StatusLine = { fg = c.fg, bg = c.bg_statusline }
groups.StatusLineNC = { fg = c.fg_gutter, bg = c.bg_statusline }
-- Window separators
groups.VertSplit = { fg = c.border }
groups.WinSeparator = { fg = c.border }
groups.WinBar = { fg = c.fg, bg = c.bg }
groups.WinBarNC = { fg = c.fg_dark, bg = c.bg }
-- Folds and columns
groups.Folded = { fg = c.fg_dark, bg = c.bg_highlight }
groups.NonText = { fg = c.neutral_700 }
groups.EndOfBuffer = { fg = c.neutral_800 }
groups.Whitespace = { fg = c.neutral_800 }
groups.SpecialKey = { fg = c.neutral_700 }
-- Messages
groups.ErrorMsg = { fg = c.error }
groups.WarningMsg = { fg = c.warning }
groups.MoreMsg = { fg = c.info }
groups.Question = { fg = c.accent }
groups.Title = { fg = c.accent, bold = true }
groups.Directory = { fg = c.func }
groups.Conceal = { fg = c.fg_dark }
groups.MatchParen = { fg = c.accent, bold = true, underline = true }
groups.QuickFixLine = { bg = c.bg_visual }
-- Float border
groups.FloatBorder = { fg = c.border_highlight, bg = c.bg_float }
groups.FloatTitle = { fg = c.accent, bg = c.bg_float, bold = true }
-- ============================================================
-- Standard syntax groups (:h group-name)
-- ============================================================
groups.Comment = { fg = c.comment, italic = true }
groups.Constant = { fg = c.constant }
groups.String = { fg = c.string }
groups.Character = { fg = c.string }
groups.Number = { fg = c.number }
groups.Boolean = { fg = c.boolean }
groups.Float = { fg = c.number }
groups.Identifier = { fg = c.variable }
groups.Function = { fg = c.func }
groups.Statement = { fg = c.keyword }
groups.Conditional = { fg = c.keyword }
groups.Repeat = { fg = c.keyword }
groups.Label = { fg = c.keyword }
groups.Operator = { fg = c.operator }
groups.Keyword = { fg = c.keyword }
groups.Exception = { fg = c.keyword }
groups.PreProc = { fg = c.preproc }
groups.Include = { fg = c.keyword }
groups.Define = { fg = c.keyword }
groups.Macro = { fg = c.preproc }
groups.PreCondit = { fg = c.preproc }
groups.Type = { fg = c.type }
groups.StorageClass = { fg = c.keyword }
groups.Structure = { fg = c.type }
groups.Typedef = { fg = c.type }
groups.Special = { fg = c.special }
groups.SpecialChar = { fg = c.escape }
groups.Tag = { fg = c.tag }
groups.Delimiter = { fg = c.punctuation }
groups.SpecialComment = { fg = c.comment, bold = true }
groups.Debug = { fg = c.warning }
groups.Underlined = { underline = true }
groups.Bold = { bold = true }
groups.Italic = { italic = true }
groups.Ignore = { fg = c.bg }
groups.Error = { fg = c.error }
groups.Todo = { fg = c.bg, bg = c.accent, bold = true }
-- ============================================================
-- Treesitter highlights (@groups)
-- ============================================================
-- Identifiers
groups["@variable"] = { fg = c.variable }
groups["@variable.builtin"] = { fg = c.builtin, italic = true }
groups["@variable.parameter"] = { fg = c.parameter }
groups["@variable.parameter.builtin"] = { fg = c.parameter, italic = true }
groups["@variable.member"] = { fg = c.property }
groups["@constant"] = { fg = c.constant }
groups["@constant.builtin"] = { fg = c.constant, bold = true }
groups["@constant.macro"] = { fg = c.preproc }
groups["@module"] = { fg = c.namespace }
groups["@module.builtin"] = { fg = c.namespace, italic = true }
groups["@label"] = { fg = c.keyword }
-- Literals
groups["@string"] = { fg = c.string }
groups["@string.documentation"] = { fg = c.comment }
groups["@string.regex"] = { fg = c.regex }
groups["@string.escape"] = { fg = c.escape }
groups["@string.special"] = { fg = c.special }
groups["@string.special.symbol"] = { fg = c.special }
groups["@string.special.url"] = { fg = c.info, underline = true }
groups["@string.special.path"] = { fg = c.info }
groups["@character"] = { fg = c.string }
groups["@character.special"] = { fg = c.escape }
groups["@boolean"] = { fg = c.boolean }
groups["@number"] = { fg = c.number }
groups["@number.float"] = { fg = c.number }
-- Types
groups["@type"] = { fg = c.type }
groups["@type.builtin"] = { fg = c.type, italic = true }
groups["@type.definition"] = { fg = c.type }
groups["@type.qualifier"] = { fg = c.keyword }
groups["@attribute"] = { fg = c.attribute }
groups["@attribute.builtin"] = { fg = c.attribute, italic = true }
groups["@property"] = { fg = c.property }
-- Functions
groups["@function"] = { fg = c.func }
groups["@function.builtin"] = { fg = c.builtin }
groups["@function.call"] = { fg = c.func }
groups["@function.macro"] = { fg = c.preproc }
groups["@function.method"] = { fg = c.func }
groups["@function.method.call"] = { fg = c.func }
groups["@constructor"] = { fg = c.type }
groups["@operator"] = { fg = c.operator }
-- Keywords
groups["@keyword"] = { fg = c.keyword }
groups["@keyword.coroutine"] = { fg = c.keyword, italic = true }
groups["@keyword.function"] = { fg = c.keyword }
groups["@keyword.operator"] = { fg = c.operator }
groups["@keyword.import"] = { fg = c.keyword }
groups["@keyword.type"] = { fg = c.keyword }
groups["@keyword.modifier"] = { fg = c.keyword }
groups["@keyword.repeat"] = { fg = c.keyword }
groups["@keyword.return"] = { fg = c.keyword }
groups["@keyword.debug"] = { fg = c.warning }
groups["@keyword.exception"] = { fg = c.keyword }
groups["@keyword.conditional"] = { fg = c.keyword }
groups["@keyword.conditional.ternary"] = { fg = c.operator }
groups["@keyword.directive"] = { fg = c.preproc }
groups["@keyword.directive.define"] = { fg = c.preproc }
-- Punctuation
groups["@punctuation.delimiter"] = { fg = c.punctuation }
groups["@punctuation.bracket"] = { fg = c.punctuation }
groups["@punctuation.special"] = { fg = c.special }
-- Comments
groups["@comment"] = { fg = c.comment, italic = true }
groups["@comment.documentation"] = { fg = c.comment }
groups["@comment.error"] = { fg = c.error }
groups["@comment.warning"] = { fg = c.warning }
groups["@comment.todo"] = { fg = c.bg, bg = c.accent, bold = true }
groups["@comment.note"] = { fg = c.bg, bg = c.info, bold = true }
-- Markup (markdown, html, etc.)
groups["@markup.strong"] = { bold = true }
groups["@markup.italic"] = { italic = true }
groups["@markup.strikethrough"] = { strikethrough = true }
groups["@markup.underline"] = { underline = true }
groups["@markup.heading"] = { fg = c.accent, bold = true }
groups["@markup.heading.1"] = { fg = c.accent, bold = true }
groups["@markup.heading.2"] = { fg = c.func, bold = true }
groups["@markup.heading.3"] = { fg = c.type, bold = true }
groups["@markup.heading.4"] = { fg = c.string, bold = true }
groups["@markup.heading.5"] = { fg = c.constant, bold = true }
groups["@markup.heading.6"] = { fg = c.comment, bold = true }
groups["@markup.quote"] = { fg = c.fg_dark, italic = true }
groups["@markup.math"] = { fg = c.number }
groups["@markup.environment"] = { fg = c.preproc }
groups["@markup.link"] = { fg = c.info, underline = true }
groups["@markup.link.label"] = { fg = c.info }
groups["@markup.link.url"] = { fg = c.info, underline = true }
groups["@markup.raw"] = { fg = c.string }
groups["@markup.raw.block"] = { fg = c.fg }
groups["@markup.list"] = { fg = c.accent }
groups["@markup.list.checked"] = { fg = c.git_add }
groups["@markup.list.unchecked"] = { fg = c.fg_dark }
-- Tags (HTML, JSX, etc.)
groups["@tag"] = { fg = c.tag }
groups["@tag.builtin"] = { fg = c.tag, bold = true }
groups["@tag.attribute"] = { fg = c.attribute }
groups["@tag.delimiter"] = { fg = c.punctuation }
-- ============================================================
-- LSP Semantic Tokens
-- ============================================================
groups["@lsp.type.boolean"] = { link = "@boolean" }
groups["@lsp.type.builtinType"] = { link = "@type.builtin" }
groups["@lsp.type.comment"] = { link = "@comment" }
groups["@lsp.type.decorator"] = { link = "@attribute" }
groups["@lsp.type.deriveHelper"] = { link = "@attribute" }
groups["@lsp.type.enum"] = { link = "@type" }
groups["@lsp.type.enumMember"] = { link = "@constant" }
groups["@lsp.type.escapeSequence"] = { link = "@string.escape" }
groups["@lsp.type.formatSpecifier"] = { link = "@punctuation.special" }
groups["@lsp.type.function"] = { link = "@function" }
groups["@lsp.type.generic"] = { link = "@variable" }
groups["@lsp.type.interface"] = { fg = c.type }
groups["@lsp.type.keyword"] = { link = "@keyword" }
groups["@lsp.type.lifetime"] = { link = "@keyword.modifier" }
groups["@lsp.type.method"] = { link = "@function.method" }
groups["@lsp.type.namespace"] = { link = "@module" }
groups["@lsp.type.number"] = { link = "@number" }
groups["@lsp.type.operator"] = { link = "@operator" }
groups["@lsp.type.parameter"] = { link = "@variable.parameter" }
groups["@lsp.type.property"] = { link = "@property" }
groups["@lsp.type.selfKeyword"] = { fg = c.builtin, italic = true }
groups["@lsp.type.selfTypeKeyword"] = { link = "@type.builtin" }
groups["@lsp.type.string"] = { link = "@string" }
groups["@lsp.type.typeAlias"] = { link = "@type.definition" }
groups["@lsp.type.typeParameter"] = { fg = c.type }
groups["@lsp.type.unresolvedReference"] = { undercurl = true, sp = c.error }
groups["@lsp.type.variable"] = {} -- defer to treesitter
groups["@lsp.typemod.class.defaultLibrary"] = { link = "@type.builtin" }
groups["@lsp.typemod.enum.defaultLibrary"] = { link = "@type.builtin" }
groups["@lsp.typemod.enumMember.defaultLibrary"] = { link = "@constant.builtin" }
groups["@lsp.typemod.function.defaultLibrary"] = { link = "@function.builtin" }
groups["@lsp.typemod.keyword.async"] = { link = "@keyword.coroutine" }
groups["@lsp.typemod.keyword.injected"] = { link = "@keyword" }
groups["@lsp.typemod.macro.defaultLibrary"] = { link = "@function.builtin" }
groups["@lsp.typemod.method.defaultLibrary"] = { link = "@function.builtin" }
groups["@lsp.typemod.operator.injected"] = { link = "@operator" }
groups["@lsp.typemod.string.injected"] = { link = "@string" }
groups["@lsp.typemod.struct.defaultLibrary"] = { link = "@type.builtin" }
groups["@lsp.typemod.type.defaultLibrary"] = { link = "@type.builtin" }
groups["@lsp.typemod.typeAlias.defaultLibrary"] = { link = "@type.builtin" }
groups["@lsp.typemod.variable.callable"] = { link = "@function" }
groups["@lsp.typemod.variable.defaultLibrary"] = { link = "@variable.builtin" }
groups["@lsp.typemod.variable.injected"] = { link = "@variable" }
groups["@lsp.typemod.variable.static"] = { link = "@constant" }
-- ============================================================
-- Diagnostics
-- ============================================================
groups.DiagnosticError = { fg = c.error }
groups.DiagnosticWarn = { fg = c.warning }
groups.DiagnosticInfo = { fg = c.info }
groups.DiagnosticHint = { fg = c.hint }
groups.DiagnosticOk = { fg = c.git_add }
groups.DiagnosticUnderlineError = { undercurl = true, sp = c.error }
groups.DiagnosticUnderlineWarn = { undercurl = true, sp = c.warning }
groups.DiagnosticUnderlineInfo = { undercurl = true, sp = c.info }
groups.DiagnosticUnderlineHint = { undercurl = true, sp = c.hint }
groups.DiagnosticUnderlineOk = { undercurl = true, sp = c.git_add }
groups.DiagnosticVirtualTextError = { fg = c.error, bg = c.danger_bg }
groups.DiagnosticVirtualTextWarn = { fg = c.warning, bg = c.sticky_bg }
groups.DiagnosticVirtualTextInfo = { fg = c.info, bg = c.info_bg }
groups.DiagnosticVirtualTextHint = { fg = c.hint, bg = c.success_bg }
-- ============================================================
-- Diff
-- ============================================================
groups.DiffAdd = { bg = c.diff_add }
groups.DiffChange = { bg = c.diff_change }
groups.DiffDelete = { bg = c.diff_delete }
groups.DiffText = { bg = c.diff_text }
groups.diffAdded = { fg = c.git_add }
groups.diffRemoved = { fg = c.git_delete }
groups.diffChanged = { fg = c.git_change }
groups.diffOldFile = { fg = c.git_delete }
groups.diffNewFile = { fg = c.git_add }
groups.diffFile = { fg = c.info }
groups.diffLine = { fg = c.comment }
groups.diffIndexLine = { fg = c.comment }
-- ============================================================
-- Git Signs
-- ============================================================
groups.GitSignsAdd = { fg = c.git_add }
groups.GitSignsChange = { fg = c.git_change }
groups.GitSignsDelete = { fg = c.git_delete }
groups.GitSignsAddNr = { fg = c.git_add }
groups.GitSignsChangeNr = { fg = c.git_change }
groups.GitSignsDeleteNr = { fg = c.git_delete }
groups.GitSignsAddLn = { bg = c.diff_add }
groups.GitSignsChangeLn = { bg = c.diff_change }
groups.GitSignsDeleteLn = { bg = c.diff_delete }
groups.GitSignsCurrentLineBlame = { fg = c.fg_gutter, italic = true }
-- ============================================================
-- Telescope
-- ============================================================
groups.TelescopeNormal = { fg = c.fg, bg = c.bg_float }
groups.TelescopeBorder = { fg = c.border_highlight, bg = c.bg_float }
groups.TelescopeTitle = { fg = c.accent, bold = true }
groups.TelescopePromptNormal = { fg = c.fg_bright, bg = c.bg_popup }
groups.TelescopePromptBorder = { fg = c.accent, bg = c.bg_popup }
groups.TelescopePromptTitle = { fg = c.bg, bg = c.accent, bold = true }
groups.TelescopePromptPrefix = { fg = c.accent }
groups.TelescopePreviewTitle = { fg = c.bg, bg = c.info, bold = true }
groups.TelescopeResultsTitle = { fg = c.bg, bg = c.hint, bold = true }
groups.TelescopeSelection = { bg = c.bg_visual }
groups.TelescopeSelectionCaret = { fg = c.accent }
groups.TelescopeMatching = { fg = c.accent, bold = true }
-- ============================================================
-- NvimTree / Neo-tree
-- ============================================================
groups.NvimTreeNormal = { fg = c.fg_sidebar, bg = c.bg_sidebar }
groups.NvimTreeNormalNC = { fg = c.fg_sidebar, bg = c.bg_sidebar }
groups.NvimTreeRootFolder = { fg = c.accent, bold = true }
groups.NvimTreeFolderIcon = { fg = c.func }
groups.NvimTreeFolderName = { fg = c.func }
groups.NvimTreeOpenedFolderName = { fg = c.func, bold = true }
groups.NvimTreeGitDirty = { fg = c.git_change }
groups.NvimTreeGitNew = { fg = c.git_add }
groups.NvimTreeGitDeleted = { fg = c.git_delete }
groups.NvimTreeSpecialFile = { fg = c.special, underline = true }
groups.NvimTreeIndentMarker = { fg = c.border }
groups.NvimTreeWinSeparator = { fg = c.border, bg = c.bg_sidebar }
groups.NeoTreeNormal = { fg = c.fg_sidebar, bg = c.bg_sidebar }
groups.NeoTreeNormalNC = { fg = c.fg_sidebar, bg = c.bg_sidebar }
groups.NeoTreeDimText = { fg = c.fg_gutter }
groups.NeoTreeTitleBar = { fg = c.bg, bg = c.accent }
-- ============================================================
-- Indent Blankline
-- ============================================================
groups.IndentBlanklineChar = { fg = c.neutral_800, nocombine = true }
groups.IndentBlanklineContextChar = { fg = c.accent, nocombine = true }
groups.IblIndent = { fg = c.neutral_800, nocombine = true }
groups.IblScope = { fg = c.accent, nocombine = true }
-- ============================================================
-- Which Key
-- ============================================================
groups.WhichKey = { fg = c.accent }
groups.WhichKeyGroup = { fg = c.func }
groups.WhichKeyDesc = { fg = c.fg }
groups.WhichKeySeparator = { fg = c.comment }
groups.WhichKeyValue = { fg = c.fg_dark }
-- ============================================================
-- Dashboard / Alpha
-- ============================================================
groups.DashboardHeader = { fg = c.accent }
groups.DashboardCenter = { fg = c.func }
groups.DashboardShortCut = { fg = c.keyword }
groups.DashboardFooter = { fg = c.comment, italic = true }
-- ============================================================
-- Notify
-- ============================================================
groups.NotifyERRORBorder = { fg = c.error }
groups.NotifyERRORIcon = { fg = c.error }
groups.NotifyERRORTitle = { fg = c.error }
groups.NotifyWARNBorder = { fg = c.warning }
groups.NotifyWARNIcon = { fg = c.warning }
groups.NotifyWARNTitle = { fg = c.warning }
groups.NotifyINFOBorder = { fg = c.info }
groups.NotifyINFOIcon = { fg = c.info }
groups.NotifyINFOTitle = { fg = c.info }
groups.NotifyDEBUGBorder = { fg = c.comment }
groups.NotifyDEBUGIcon = { fg = c.comment }
groups.NotifyDEBUGTitle = { fg = c.comment }
groups.NotifyTRACEBorder = { fg = c.special }
groups.NotifyTRACEIcon = { fg = c.special }
groups.NotifyTRACETitle = { fg = c.special }
-- ============================================================
-- Noice
-- ============================================================
groups.NoiceCmdlinePopup = { fg = c.fg, bg = c.bg_float }
groups.NoiceCmdlinePopupBorder = { fg = c.accent }
groups.NoiceCmdlineIcon = { fg = c.accent }
groups.NoiceConfirm = { fg = c.fg, bg = c.bg_float }
groups.NoiceConfirmBorder = { fg = c.accent }
-- ============================================================
-- Cmp (completion)
-- ============================================================
groups.CmpItemAbbr = { fg = c.fg }
groups.CmpItemAbbrDeprecated = { fg = c.fg_dark, strikethrough = true }
groups.CmpItemAbbrMatch = { fg = c.accent, bold = true }
groups.CmpItemAbbrMatchFuzzy = { fg = c.accent, bold = true }
groups.CmpItemKind = { fg = c.func }
groups.CmpItemMenu = { fg = c.fg_dark }
groups.CmpItemKindClass = { fg = c.type }
groups.CmpItemKindColor = { fg = c.special }
groups.CmpItemKindConstant = { fg = c.constant }
groups.CmpItemKindConstructor = { fg = c.type }
groups.CmpItemKindEnum = { fg = c.type }
groups.CmpItemKindEnumMember = { fg = c.constant }
groups.CmpItemKindEvent = { fg = c.special }
groups.CmpItemKindField = { fg = c.property }
groups.CmpItemKindFile = { fg = c.fg }
groups.CmpItemKindFolder = { fg = c.func }
groups.CmpItemKindFunction = { fg = c.func }
groups.CmpItemKindInterface = { fg = c.type }
groups.CmpItemKindKeyword = { fg = c.keyword }
groups.CmpItemKindMethod = { fg = c.func }
groups.CmpItemKindModule = { fg = c.namespace }
groups.CmpItemKindOperator = { fg = c.operator }
groups.CmpItemKindProperty = { fg = c.property }
groups.CmpItemKindReference = { fg = c.constant }
groups.CmpItemKindSnippet = { fg = c.special }
groups.CmpItemKindStruct = { fg = c.type }
groups.CmpItemKindText = { fg = c.fg }
groups.CmpItemKindTypeParameter = { fg = c.type }
groups.CmpItemKindUnit = { fg = c.constant }
groups.CmpItemKindValue = { fg = c.constant }
groups.CmpItemKindVariable = { fg = c.variable }
-- ============================================================
-- Blink.cmp
-- ============================================================
groups.BlinkCmpMenu = { fg = c.fg, bg = c.bg_popup }
groups.BlinkCmpMenuBorder = { fg = c.border_highlight, bg = c.bg_popup }
groups.BlinkCmpMenuSelection = { bg = c.bg_visual }
groups.BlinkCmpLabel = { fg = c.fg }
groups.BlinkCmpLabelMatch = { fg = c.accent, bold = true }
groups.BlinkCmpKind = { fg = c.func }
-- ============================================================
-- Trouble
-- ============================================================
groups.TroubleNormal = { fg = c.fg, bg = c.bg_sidebar }
groups.TroubleNormalNC = { fg = c.fg_sidebar, bg = c.bg_sidebar }
-- ============================================================
-- Lazy
-- ============================================================
groups.LazyButton = { fg = c.fg, bg = c.bg_popup }
groups.LazyButtonActive = { fg = c.bg, bg = c.accent }
groups.LazyH1 = { fg = c.bg, bg = c.accent, bold = true }
groups.LazySpecial = { fg = c.accent }
groups.LazyProgressDone = { fg = c.accent }
groups.LazyProgressTodo = { fg = c.fg_gutter }
-- ============================================================
-- Mason
-- ============================================================
groups.MasonNormal = { fg = c.fg, bg = c.bg_float }
groups.MasonHeader = { fg = c.bg, bg = c.accent, bold = true }
groups.MasonHighlight = { fg = c.accent }
groups.MasonHighlightBlock = { fg = c.bg, bg = c.accent }
groups.MasonMutedBlock = { fg = c.fg, bg = c.bg_popup }
-- ============================================================
-- Mini plugins
-- ============================================================
groups.MiniStatuslineFilename = { fg = c.fg, bg = c.bg_statusline }
groups.MiniStatuslineDevinfo = { fg = c.fg_dark, bg = c.bg_highlight }
groups.MiniStatuslineModeNormal = { fg = c.bg, bg = c.accent, bold = true }
groups.MiniStatuslineModeInsert = { fg = c.bg, bg = c.hint, bold = true }
groups.MiniStatuslineModeVisual = { fg = c.bg, bg = c.special, bold = true }
groups.MiniStatuslineModeReplace = { fg = c.bg, bg = c.error, bold = true }
groups.MiniStatuslineModeCommand = { fg = c.bg, bg = c.warning, bold = true }
groups.MiniIndentscopeSymbol = { fg = c.accent }
groups.MiniCursorword = { bg = c.bg_visual }
groups.MiniCursorwordCurrent = { bg = c.bg_visual }
-- ============================================================
-- Bufferline
-- ============================================================
groups.BufferLineIndicatorSelected = { fg = c.accent }
groups.BufferLineFill = { bg = c.bg_dark }
-- ============================================================
-- Snacks
-- ============================================================
groups.SnacksDashboardHeader = { fg = c.accent }
groups.SnacksDashboardFooter = { fg = c.comment, italic = true }
groups.SnacksDashboardIcon = { fg = c.func }
groups.SnacksDashboardKey = { fg = c.accent }
groups.SnacksDashboardDesc = { fg = c.fg }
groups.SnacksNotifierInfo = { fg = c.info }
groups.SnacksNotifierWarn = { fg = c.warning }
groups.SnacksNotifierError = { fg = c.error }
return groups
end
return M

130
init.lua Normal file
View File

@ -0,0 +1,130 @@
local M = {}
---@class N8nConfig
M.config = {
transparent = false, -- enable transparent background
terminal_colors = true, -- set terminal colors
dim_inactive = false, -- dim inactive windows
styles = {
comments = { italic = true },
keywords = {},
functions = {},
strings = {},
variables = {},
},
---@type table<string, vim.api.keyset.highlight>
on_highlights = function(highlights, colors) end,
---@type fun(colors: table): table
on_colors = function(colors) return colors end,
}
---@param opts? N8nConfig
function M.setup(opts)
M.config = vim.tbl_deep_extend("force", M.config, opts or {})
end
function M.load()
if vim.g.colors_name then
vim.cmd("hi clear")
end
if vim.fn.exists("syntax_on") then
vim.cmd("syntax reset")
end
vim.o.termguicolors = true
vim.g.colors_name = "n8n"
vim.o.background = "dark"
local theme = require("n8n.theme")
local groups_mod = require("n8n.groups")
local c = theme.colors()
-- Let user modify colors
if type(M.config.on_colors) == "function" then
local custom = M.config.on_colors(c)
if custom then
c = vim.tbl_deep_extend("force", c, custom)
end
end
-- Handle transparent mode
if M.config.transparent then
c.bg = "NONE"
c.bg_sidebar = "NONE"
c.bg_float = "NONE"
end
local groups = groups_mod.setup({ colors = c })
-- Apply style overrides
if M.config.styles.comments then
groups.Comment = vim.tbl_extend("force", groups.Comment or {}, M.config.styles.comments)
groups["@comment"] = vim.tbl_extend("force", groups["@comment"] or {}, M.config.styles.comments)
end
if M.config.styles.keywords then
for _, key in ipairs({ "Keyword", "Statement", "Conditional", "Repeat", "@keyword" }) do
if groups[key] then
groups[key] = vim.tbl_extend("force", groups[key], M.config.styles.keywords)
end
end
end
if M.config.styles.functions then
for _, key in ipairs({ "Function", "@function", "@function.call" }) do
if groups[key] then
groups[key] = vim.tbl_extend("force", groups[key], M.config.styles.functions)
end
end
end
if M.config.styles.strings then
for _, key in ipairs({ "String", "@string" }) do
if groups[key] then
groups[key] = vim.tbl_extend("force", groups[key], M.config.styles.strings)
end
end
end
if M.config.styles.variables then
for _, key in ipairs({ "Identifier", "@variable" }) do
if groups[key] then
groups[key] = vim.tbl_extend("force", groups[key], M.config.styles.variables)
end
end
end
-- Dim inactive windows
if M.config.dim_inactive then
groups.NormalNC = { fg = c.fg_dark, bg = c.bg_dark }
end
-- Let user modify highlights
if type(M.config.on_highlights) == "function" then
M.config.on_highlights(groups, c)
end
-- Apply all highlight groups
for group, hl in pairs(groups) do
vim.api.nvim_set_hl(0, group, hl)
end
-- Set terminal colors
if M.config.terminal_colors then
local t = theme.colors().terminal
vim.g.terminal_color_0 = t.black
vim.g.terminal_color_1 = t.red
vim.g.terminal_color_2 = t.green
vim.g.terminal_color_3 = t.yellow
vim.g.terminal_color_4 = t.blue
vim.g.terminal_color_5 = t.magenta
vim.g.terminal_color_6 = t.cyan
vim.g.terminal_color_7 = t.white
vim.g.terminal_color_8 = t.bright_black
vim.g.terminal_color_9 = t.bright_red
vim.g.terminal_color_10 = t.bright_green
vim.g.terminal_color_11 = t.bright_yellow
vim.g.terminal_color_12 = t.bright_blue
vim.g.terminal_color_13 = t.bright_magenta
vim.g.terminal_color_14 = t.bright_cyan
vim.g.terminal_color_15 = t.bright_white
end
end
return M

View File

@ -0,0 +1,55 @@
local palette = require("n8n.palette")
local n8n = {}
local c = {
bg = palette.neutral_950,
bg_light = palette.neutral_900,
fg = palette.neutral_200,
fg_dim = palette.neutral_500,
accent = palette.orange_300,
green = palette.green_500,
blue = palette.blue_400,
purple = palette.purple_500,
red = palette.red_500,
yellow = palette.yellow_500,
}
n8n.normal = {
a = { bg = c.accent, fg = c.bg, gui = "bold" },
b = { bg = c.bg_light, fg = c.accent },
c = { bg = c.bg, fg = c.fg_dim },
}
n8n.insert = {
a = { bg = c.green, fg = c.bg, gui = "bold" },
b = { bg = c.bg_light, fg = c.green },
}
n8n.visual = {
a = { bg = c.purple, fg = c.bg, gui = "bold" },
b = { bg = c.bg_light, fg = c.purple },
}
n8n.replace = {
a = { bg = c.red, fg = c.bg, gui = "bold" },
b = { bg = c.bg_light, fg = c.red },
}
n8n.command = {
a = { bg = c.yellow, fg = c.bg, gui = "bold" },
b = { bg = c.bg_light, fg = c.yellow },
}
n8n.terminal = {
a = { bg = c.blue, fg = c.bg, gui = "bold" },
b = { bg = c.bg_light, fg = c.blue },
}
n8n.inactive = {
a = { bg = c.bg, fg = c.fg_dim },
b = { bg = c.bg, fg = c.fg_dim },
c = { bg = c.bg, fg = c.fg_dim },
}
return n8n

1
n8n.lua Normal file
View File

@ -0,0 +1 @@
require("n8n").load()

151
palette.lua Normal file
View File

@ -0,0 +1,151 @@
---@class N8nPalette
local M = {}
-- n8n Design System palette
-- Extracted from the official n8n dark theme CSS custom properties
-- Neutrals (the backbone of n8n's dark UI)
M.neutral_white = "#ffffff"
M.neutral_50 = "#fcfcfc"
M.neutral_100 = "#f9f9f9"
M.neutral_125 = "#f5f5f5"
M.neutral_150 = "#ededed"
M.neutral_200 = "#e0e0e0"
M.neutral_250 = "#cccccc"
M.neutral_300 = "#adadad"
M.neutral_400 = "#949494"
M.neutral_500 = "#828282"
M.neutral_600 = "#757575"
M.neutral_700 = "#4d4d4d"
M.neutral_750 = "#424242"
M.neutral_800 = "#3d3d3d"
M.neutral_850 = "#2b2b2b"
M.neutral_900 = "#212121"
M.neutral_950 = "#171717"
M.neutral_black = "#000000"
-- Orange (n8n's brand accent in dark mode)
M.orange_50 = "#fff6f5"
M.orange_100 = "#ffe8e6"
M.orange_150 = "#ffdfdb"
M.orange_200 = "#ffc9c2"
M.orange_250 = "#ff9c8f"
M.orange_300 = "#ff6f5c"
M.orange_400 = "#ff4b33"
M.orange_500 = "#ff1e00"
M.orange_600 = "#cc1800"
M.orange_700 = "#991200"
M.orange_800 = "#660c00"
M.orange_900 = "#330600"
M.orange_950 = "#1a0300"
-- Purple (used for pinned nodes, code syntax, accents)
M.purple_50 = "#f8f7fc"
M.purple_100 = "#edecf8"
M.purple_200 = "#e6e4f6"
M.purple_300 = "#c3bee9"
M.purple_400 = "#a098dc"
M.purple_500 = "#7d72cf"
M.purple_600 = "#5a4cc2"
M.purple_700 = "#4438a3"
M.purple_800 = "#3b308d"
M.purple_900 = "#302772"
M.purple_950 = "#211b50"
-- Green (success states, boolean/number syntax)
M.green_50 = "#ebfaf1"
M.green_100 = "#d6f5e3"
M.green_200 = "#adebc7"
M.green_300 = "#85e0ac"
M.green_400 = "#5cd693"
M.green_500 = "#33cc78"
M.green_600 = "#29a360"
M.green_700 = "#1f7a48"
M.green_800 = "#19663d"
M.green_900 = "#145231"
M.green_950 = "#0a291a"
-- Mint (chat user backgrounds, alternative green)
M.mint_500 = "#16e975"
M.mint_600 = "#13cd67"
M.mint_700 = "#0d8c46"
-- Red (danger, errors, diagnostics)
M.red_50 = "#fef1f2"
M.red_100 = "#fcdadd"
M.red_200 = "#faccd0"
M.red_250 = "#f7abb1"
M.red_300 = "#f5949c"
M.red_400 = "#f16a75"
M.red_500 = "#ee4452"
M.red_600 = "#ea1f30"
M.red_700 = "#c41221"
M.red_800 = "#ad101d"
M.red_900 = "#7e0c15"
M.red_950 = "#4f070d"
-- Blue (info states, links, selections)
M.blue_50 = "#eaf2fb"
M.blue_100 = "#ddebf8"
M.blue_200 = "#b2d1f0"
M.blue_300 = "#7fb3e6"
M.blue_400 = "#4891db"
M.blue_500 = "#2878c8"
M.blue_600 = "#236bb3"
M.blue_700 = "#1b5288"
M.blue_800 = "#13385e"
M.blue_900 = "#081a2b"
-- Yellow (warnings, sticky notes)
M.yellow_100 = "#fff5d6"
M.yellow_200 = "#ffe48a"
M.yellow_300 = "#ffdb66"
M.yellow_400 = "#ffcf33"
M.yellow_500 = "#ffc400"
M.yellow_600 = "#cc9c00"
M.yellow_700 = "#a37d00"
M.yellow_800 = "#705600"
M.yellow_900 = "#332700"
-- Gold (alternative warm accent)
M.gold_50 = "#fdf9f1"
M.gold_100 = "#f7e3c4"
M.gold_200 = "#f4d8ae"
M.gold_300 = "#efc381"
M.gold_400 = "#e6a23d"
M.gold_500 = "#d48a1c"
M.gold_600 = "#b57617"
M.gold_700 = "#905e13"
M.gold_800 = "#63410d"
M.gold_900 = "#2d1d06"
-- Slate (alternative neutral with blue tint)
M.slate_100 = "#e4e3e8"
M.slate_200 = "#c8c7d1"
M.slate_300 = "#adabba"
M.slate_400 = "#928fa3"
M.slate_500 = "#76738c"
M.slate_600 = "#5f5c70"
M.slate_700 = "#474554"
M.slate_800 = "#2f2e38"
M.slate_900 = "#18171c"
-- n8n brand pink (logo, marketing)
M.brand_pink = "#EA4B71"
M.brand_dark = "#101330"
-- Assistant/AI highlight gradient stops
M.highlight_1 = "#8c90f2"
M.highlight_2 = "#a977f0"
M.highlight_3 = "#f0778b"
-- Node icon accent colors (from the canvas)
M.icon_blue = "#898fff"
M.icon_light_blue = "#58abff"
M.icon_dark_blue = "#7ba7ff"
M.icon_pink_red = "#f85d82"
-- Special
M.none = "NONE"
return M

118
theme.lua Normal file
View File

@ -0,0 +1,118 @@
local palette = require("n8n.palette")
---@class N8nTheme
local M = {}
function M.colors()
return {
-- Editor backgrounds (mirroring n8n's layered dark surfaces)
bg = palette.neutral_950, -- #171717 canvas background
bg_dark = palette.neutral_black, -- #000000 deepest bg
bg_float = palette.neutral_900, -- #212121 floating panels
bg_popup = palette.neutral_850, -- #2b2b2b popup menus
bg_sidebar = palette.neutral_900, -- #212121 sidebar/tree
bg_statusline = palette.neutral_900, -- #212121 statusline
bg_visual = palette.neutral_750, -- #424242 visual selection
bg_highlight = palette.neutral_850, -- #2b2b2b cursorline
bg_search = palette.yellow_900, -- #332700 search highlight bg
-- Foregrounds
fg = palette.neutral_200, -- #e0e0e0 primary text
fg_bright = palette.neutral_white, -- #ffffff bright text
fg_dark = palette.neutral_400, -- #949494 muted text
fg_gutter = palette.neutral_600, -- #757575 gutter/line numbers
fg_sidebar = palette.neutral_300, -- #adadad sidebar text
-- Borders (n8n uses white alpha overlays on dark)
border = palette.neutral_700, -- #4d4d4d default border
border_highlight = palette.neutral_600, -- #757575 active border
-- Brand accent (n8n orange in dark mode)
accent = palette.orange_300, -- #ff6f5c brand accent
accent_hover = palette.orange_400, -- #ff4b33 hover state
accent_active = palette.orange_500, -- #ff1e00 active/pressed
-- Syntax colors (derived from n8n's code editor + JSON viewer)
keyword = palette.orange_300, -- #ff6f5c keywords (brand color)
string = palette.purple_400, -- #a098dc strings
func = palette.blue_300, -- #7fb3e6 functions
variable = palette.neutral_200, -- #e0e0e0 variables
constant = palette.gold_400, -- #e6a23d constants
number = palette.green_600, -- #29a360 numbers
boolean = palette.green_600, -- #29a360 booleans
type = palette.purple_300, -- #c3bee9 types
operator = palette.orange_250, -- #ff9c8f operators
property = palette.blue_400, -- #4891db properties/fields
parameter = palette.gold_300, -- #efc381 parameters
comment = palette.neutral_500, -- #828282 comments
punctuation = palette.neutral_400, -- #949494 punctuation
tag = palette.orange_300, -- #ff6f5c HTML/XML tags
attribute = palette.purple_400, -- #a098dc attributes
namespace = palette.slate_300, -- #adabba namespaces
preproc = palette.brand_pink, -- #EA4B71 preprocessor
special = palette.highlight_2, -- #a977f0 special symbols
regex = palette.mint_500, -- #16e975 regex
escape = palette.mint_600, -- #13cd67 escape sequences
builtin = palette.blue_300, -- #7fb3e6 built-in functions
-- JSON viewer colors (directly from n8n's JSON display)
json_key = palette.neutral_125, -- #f5f5f5 JSON keys
json_string = palette.purple_400, -- #a098dc JSON string values
json_number = palette.green_600, -- #29a360 JSON numbers
json_boolean = palette.green_600, -- #29a360 JSON booleans
json_null = palette.red_400, -- #f16a75 JSON null
json_bracket = palette.neutral_700, -- #4d4d4d brackets
-- Diagnostics
error = palette.red_600, -- #ea1f30
warning = palette.yellow_500, -- #ffc400
info = palette.blue_500, -- #2878c8
hint = palette.green_500, -- #33cc78
-- Diff
diff_add = palette.green_950, -- #0a291a
diff_change = palette.blue_900, -- #081a2b
diff_delete = palette.red_950, -- #4f070d
diff_text = palette.blue_800, -- #13385e
-- Git signs
git_add = palette.green_500, -- #33cc78
git_change = palette.blue_400, -- #4891db
git_delete = palette.red_500, -- #ee4452
-- n8n-specific semantic colors
node_bg = palette.neutral_850, -- node card background
node_border = palette.purple_500, -- pinned node border
sticky_bg = palette.yellow_900, -- sticky note background
sticky_border = palette.yellow_800, -- sticky note border
sticky_text = palette.neutral_125, -- sticky note text
success_bg = palette.green_950, -- success state bg
success_fg = palette.green_50, -- success state text
danger_bg = palette.red_950, -- danger state bg
danger_fg = palette.red_50, -- danger state text
info_bg = palette.blue_900, -- info state bg
info_fg = palette.blue_50, -- info state text
-- Terminal colors
terminal = {
black = palette.neutral_950,
red = palette.red_500,
green = palette.green_500,
yellow = palette.yellow_500,
blue = palette.blue_400,
magenta = palette.purple_500,
cyan = palette.mint_500,
white = palette.neutral_200,
bright_black = palette.neutral_600,
bright_red = palette.red_400,
bright_green = palette.green_400,
bright_yellow = palette.yellow_300,
bright_blue = palette.blue_300,
bright_magenta = palette.purple_400,
bright_cyan = palette.mint_600,
bright_white = palette.neutral_white,
},
}
end
return M