Examples in this documentation assumes the following variables available in context.
local file = require('new-item.items').FileItem
local cmd = require('new-item.items').CmdItem
local groups = require('new-item.groups')Note that this documentation primarily uses a imperative style because it can imply more details about the context.
However, both declarative and imperative style are available using new-item.Config.setup, you can pick one preferred.
Some action might require imperative style anyway(overriding item for example).
Note
new-item.Config.setup.groups is initialized before the invocation of new-item.Config.setup.init.
local file = require('new-item.items').FileItem
local cmd = require('new-item.items').CmdItem
require('new-item').setup {
-- declarative style
groups = {
my_group = {
visible = true,
items = {
file { ... }
cmd { ... }
}
}
},
-- imperative style
init = function(groups, ctors)
groups.my_group = {
visible = true,
items = {
ctors.file { ... }
ctors.cmd { ... }
}
}
end
}An Item is a template knows how to create a thing, an ItemGroup is a dynamically conditioned container for items.
This plugin was written in a object-oriented style, each type of item was derived from new-item.Item, any kind of item has the following fields:
id: identifier for the item.suffixandprefix: parts around the name of item.- for example, to create a typescript test file, the
suffixcan be.test.ts, and the final name would be<name>.test.ts.
- for example, to create a typescript test file, the
nameable: indicating whether the item can have a custom name.- for example, a
.gitignoreis always.gitignore, it should be notnameable. - if an item is not
nameable, it must have adefault_name.
- for example, a
default_name: a default value for the name, can evaluate dynamically.default_nameis the pre-filled input ofvim.ui.inputduring creation when the item isnameable.
cwd: the folder where the item would be created at, defaults to parent of current buffer.edit: presume the item created is a file, and open it after creation.extra_args: a key-value pair of argument specification, each argument will have an input request during creation.- You can access these argument values in
ctxof each phase CmdItemwill generate special variables like$ITEM_<uppercase_name>to interpolate inCmdItem.args
- You can access these argument values in
before_create(item, ctx): to perform a transformation before actually creating the item- you may transform things like the content or path, even the item template itself(it's an copy of original one)
after_create(item, ctx): to perform after creation
Item base definition
---@class new-item.Item
---@field label string Name displayed as entry in picker
---@field desc? string Description of the item
---@field invoke? fun(self: self) Activate the creation for this item
---@field cwd? fun(): string Returns which parent folder to create the file, default to parent of current buffer
---@field extra_args? table<string, new-item.ItemCreationArgument> Extra argument names to be specified on creation
---@field before_create? fun(self: new-item.AnyItem, ctx: new-item.ItemCreationContext)
---@field after_create? fun(self: new-item.AnyItem, ctx: new-item.ItemCreationContext)
---@field nameable? boolean True if the file item should have a custom name on creation
---@field default_name? string | fun(): string Default name of the item to be created
---@field suffix? string Trailing content of the constructed item name. Can be file extension such as `.lua` or suffix like `.test.ts`
---@field prefix? string Leading content of the constructed item nameFileItem: creating item from string content.
FileItem definition
---@class (exact) new-item.FileItem : new-item.Item
---@field filetype? string
---@field content? string
---@field edit? boolean Use :edit to create a buffer with pre-fill content instead of direct creation
---@field link? string | fun(): string Use content from another existing file
---@overload fun(o: new-item.FileItem): new-item.FileItemgroups.javascript:append { -- assuming javascript is a existing item group
file {
id = 'javascript',
label = 'javascript file',
content = 'console.log("%s")', -- %s will be replaced by name input
filetype = 'javascript', -- for treesitter highlighting
suffix = '.js' -- extension of the file
}
file {
id = 'prettierrc',
label = '.prettierrc',
edit = false, -- do not create the file directly but open a buffer with content
link = vim.fn.expand('~/.prettierrc'), -- use content of an existing file
nameable = false, -- .prettierrc is always .prettierrc
default_name = '.prettierrc',
filetype = 'json',
cwd = function() return vim.fn.getcwd() end, -- should always add to project root
},
}
groups.md:append {
-- use the file name as top level title
file {
id = 'markdown',
label = 'Markdown file',
filetype = 'markdown',
suffix = '.md',
content = [[# %s]],
},
}CmdItem: creating item by executing a shell command, and we can presume the item created is a file.
CmdItem definition
---@class (exact) new-item.CmdItem : new-item.Item
---@field exe (string | fun(): string) executable name/path
---@field args (string | fun(): string)[] command args
---@field edit? boolean Whether to open the item after creation, default to true
---@field env? (table<string, string> | fun(): table<string, string>) environment variables
---@overload fun(o: new-item.CmdItem): new-item.CmdItemnew-item.CmdItem has special variables available to be expanded in CmdItem.args field.
$ITEM_NAME: equivalent toctx.name_inputoritem.default_name$ITEM_CWD: equivalent toctx.cwd$ITEM_SUFFIX: equivalent toitem.suffix$ITEM_PREFIX: equivalent toitem.prefix$ITEM_<uppercase_name>: equivalent to values ofctx.args(in uppercase)
The following examples shows how it wrap dotnet new command as a template.
groups.dotnet:append {
cmd {
id = 'buildtargets',
label = 'Directory.Build.targets',
nameable = false,
default_name = 'Directory.Build.targets',
exe = 'dotnet',
args = { 'new', 'buildtargets' },
},
cmd {
id = 'slnx',
label = 'slnx',
exe = 'dotnet',
args = { 'new', 'sln', '--format', 'slnx', '--name', '$ITEM_NAME' },
suffix = '.slnx',
default_name = function() return vim.fs.basename(vim.fn.getcwd()) end, -- use root folder name as default
}
}You can transform item and ctx on before_create to let it be a context-aware template.
A context contains temporary values generated during the creation, such as name_input, cwd etc.
ItemCreationContext definition
---@class new-item.ItemCreationContext
---@field name_input? string name specified from vim.ui.input
---@field args? table<string, string> args input from vim.ui.input
---@field path? string path of the item to be created
---@field cwd? string the folder where the item would be created at
---@field buf? integer the buffer number created for the itemThe following example shows how to use a FileItem create a new C# class using its current folder structure as namespace.
groups.dotnet:append {
file {
label = 'class',
suffix = '.cs',
filetype = 'cs',
content = vim.text.dedent(0, [[
namespace <namespace>;
public class %s { }
]]),
before_create = function(item, ctx)
local proj
vim.fs.root(ctx.cwd, function(name, path)
if name:match('%.%w+proj$') then proj = vim.fs.joinpath(path, name) end
end)
local root_ns, ns
vim.system({ 'dotnet', 'msbuild', proj, '-getProperty:RootNamespace' }, { text = true },
function(out)
if out.code == 0 then root_ns = vim.trim(out.stdout) end
end):wait()
local rel = vim.fs.relpath(vim.fs.dirname(proj), ctx.cwd)
if rel and rel ~= '.' then
ns = root_ns .. '.' .. rel:gsub('/', '.')
else
ns = root_ns
end
item.content = item.content:gsub('<namespace>', ns)
end,
},
}item.extra_args are arguments other than input name, to be specified during prompt.
You can set a default value and description for the prompt.
---@class new-item.ItemCreationArgument
---@field default? string | fun(): string
---@field desc? string
---@field complete? fun(lead: string, cmdline: string, position: integer): string[] see :h command-completion-customlistitem.extra_args is available for all item types, you can access those argument values from ctx.args after prompt.
Each name of the args will generate a corresponding variable $ITEM_<uppercase_name> to be expanded in CmdItem.args.
cmd {
-- ...
exe = 'dotnet',
args = {
'new',
'global.json'
'--sdk-version',
'$ITEM_SDK_VERSION', -- access the special value in uppercase
},
extra_args = {
sdk_version = { -- this name will generate a special variable `$ITEM_SDK_VERSION`
desc = '--sdk-version',
default = function() -- pre-fill sdk_version as the default sdk on the system
return vim.trim(vim.fn.system { 'dotnet', '--version' })
end,
complete = function() -- completes sdk_version by all dotnet sdk available on your machine
return vim
.iter(vim.fn.systemlist { 'dotnet', '--list-sdks' })
:map(function(line)
-- xx.x.xxx [path/to/share/dotnet/sdk]
return vim.split(line, '%s+')[1]
end)
:totable()
end
},
},
before_create = function(item, ctx)
_ = ctx.args.sdk_version -- you may access its input value from ctx
end
}Important
The name of each extra argument must be a valid lua identifier(any string of letters, digits, and underscores, not beginning with a digit) due to the limitation of the internal design and neovim api.
For example sdk_version is a good name while sdk version and sdk-version are bad.
group.<id>:override allows to modify the item specification with final and prev states.
final is the current state of the item(to be modified), prev is the original state of the item.
The following example is how you can append extra operation to before_create phase of item buildprops, from dotnet group.
groups.dotnet.buildprops:override(function(final, prev)
final.before_create = function(item, ctx)
-- additional operations...
prev.before_create(item, ctx)
end
end)If loading a group involves asynchronous operation, you would need to bind a callback using ItemGroup.on_loaded to do the override.
groups.dotnet:on_loaded(function(self)
self.buildprops:override(function(final, prev)
final.before_create = function(item, ctx)
-- additional operations...
prev.before_create(item, ctx)
end
end)
end)Each item must be of certain group, each group has a visible field to be evaluated dynamically to indicate whether its contained items should present each time your invoke the picker.
visible(): boolean: indicating whether its items should present in picker.items: user-defined templates.append(self, items): append extra templates toitemgroup.itemslist.
For example, you may require javascript templates to present only when it found a package.json file on root.
groups.javascript = {
visible = function()
return vim.fs.root(vim.fn.expand('%:p:h'), 'package.json') ~= nil
end,
items = {--[[...]]}
}You can add any number of groups for your specific working environments.
ItemGroup was designed as a proxy table, so it has a dedicated method ItemGroup:override to alter its state.
That is, do not assign or alter any field to an ItemGroup with dot accessor, use override instead.
group:override {
visible = true,
}---@class new-item.ItemSource
---@field [1] string | fun(add_items: fun(items: new-item.AnyItem[])): new-item.AnyItem[]?
---@field name stringAn ItemSource can presented as three kinds of data type with name as its identifier.
- Module name: a module that returns a list of items.
- Function: a function returns a list of items.
- Function with callback: a function uses a callback to add items, useful for asynchronous scenarios.
---@class new-item.ItemSource
---@field [1] string | fun(add_items: fun(items: new-item.AnyItem[])): new-item.AnyItem[]?
---@field name string
groups.my_group = {
sources = {
{ name = 'foo', 'new-item.foo' },
{
name = 'bar',
function()
-- return a list of items
return { file { ... }, cmd { ... } }
end,
},
{
name = 'baz',
function(add_items)
-- add_items(items) is a callback to add items to the group
do_something_with_callback(add_items)
end
},
}
}- An item name was decided by either
ctx.name_inputordefault_name, depending on whether the template isnameable. - Path of the item to be created was formatted as the
${ctx.cwd}/${item.prefix}${ctx.name_input ?? item.default_name}${item.prefix} before_createwas then triggered, might perform some transformation.item:invoke()was triggered to create the item.after_createwas triggered to perform a post action.