Lua scripting for Neovim and WezTerm configuration - language patterns, vim API, and config management When user works with .lua files, mentions Lua, Neovim config, WezTerm config, vim.api, or Lua scripting
vim.lsp.config() and vim.lsp.enable() replace nvim-lspconfig for basic setupsvim.lsp.completion.enable() provides built-in auto-completiongrn (rename), grr (references), gri (implementation), gO (symbols), (code actions)gravim.snippet nodes in insert modewinborder option: Set default borders for all floating windowsvim.iter(): Generic iterator interface for tables and iterator functionsvim.snippet: Built-in snippet expansion and navigationvim.ringbuf(): Generic ring buffer data structurevim.ui.open(): Open URIs with system default handlerLua serves as the primary configuration and extension language for Neovim and WezTerm. Neovim uses LuaJIT (Lua 5.1 compatible), while WezTerm embeds Lua 5.4. Both use Lua's table-based configuration model, but their APIs differ significantly.
Key distinction: Write Neovim Lua targeting Lua 5.1/LuaJIT semantics. Write WezTerm Lua targeting Lua 5.4 semantics. Avoid Lua 5.4 features (integers, to-be-closed variables, generational GC control) in Neovim code.
-- Array-style (1-indexed)
local list = { 'a', 'b', 'c' }
print(#list) -- 3
-- Dictionary-style
local map = { name = 'value', ['key-with-dash'] = true }
-- Mixed
local mixed = { 'first', key = 'val', 'second' }
-- Nested
local config = {
ui = { border = 'rounded', width = 80 },
keys = { '<leader>f', '<leader>g' },
}
-- Table manipulation
table.insert(list, 'd') -- append
table.insert(list, 2, 'x') -- insert at position
table.remove(list, 1) -- remove at position
table.sort(list) -- in-place sort
table.concat(list, ', ') -- join to string
-- Named function
local function greet(name)
return 'Hello, ' .. name
end
-- Anonymous / closure
local counter = (function()
local count = 0
return function()
count = count + 1
return count
end
end)()
-- Variadic
local function log(level, ...)
local args = { ... }
print(string.format('[%s] %s', level, table.concat(args, ' ')))
end
-- Method syntax (colon passes self)
local obj = { name = 'test' }
function obj:get_name()
return self.name
end
local Vector = {}
Vector.__index = Vector
function Vector.new(x, y)
return setmetatable({ x = x, y = y }, Vector)
end
function Vector:length()
return math.sqrt(self.x^2 + self.y^2)
end
function Vector.__add(a, b)
return Vector.new(a.x + b.x, a.y + b.y)
end
function Vector:__tostring()
return string.format('(%g, %g)', self.x, self.y)
end
-- Lua patterns (NOT regex)
-- Character classes: %a (letter), %d (digit), %w (alphanumeric), %s (space), %p (punctuation)
-- Uppercase = complement: %A (non-letter), %D (non-digit)
string.find('hello world', 'world') -- 7, 11
string.match('key=value', '(%w+)=(%w+)') -- 'key', 'value'
string.gmatch('a,b,c', '[^,]+') -- iterator: 'a', 'b', 'c'
string.gsub('hello', 'l', 'L') -- 'heLLo', 2
string.format('%s has %d items', 'list', 5) -- 'list has 5 items'
-- Protected call
local ok, result = pcall(function()
return risky_operation()
end)
if not ok then
print('Error: ' .. result)
end
-- With error handler (gets stack trace)
local ok, result = xpcall(risky_fn, debug.traceback)
-- Assert pattern (common in Neovim)
local value = assert(some_function(), 'Expected non-nil result')
-- Result-or-error pattern
local function safe_read(path)
local f, err = io.open(path, 'r')
if not f then return nil, err end
local content = f:read('*a')
f:close()
return content
end
-- Define a module
local M = {}
function M.setup(opts)
-- configure
end
function M.run()
-- execute
end
return M
-- Use a module
local mymod = require('mymod')
mymod.setup({ option = true })
vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.shiftwidth = 2
vim.opt.expandtab = true
vim.opt.smartindent = true
vim.opt.wrap = false
vim.opt.signcolumn = 'yes'
vim.opt.completeopt = { 'menu', 'menuone', 'noselect' }
vim.opt.wildignore:append({ '*.o', '*.pyc', 'node_modules' })
-- Buffer/window local
vim.bo.filetype = 'lua'
vim.wo.foldmethod = 'expr'
-- vim.keymap.set(mode, lhs, rhs, opts)
vim.keymap.set('n', '<leader>ff', function()
require('telescope.builtin').find_files()
end, { desc = 'Find files' })
vim.keymap.set('n', '<Esc>', '<cmd>nohlsearch<CR>', { desc = 'Clear search highlight' })
vim.keymap.set({ 'n', 'v' }, '<leader>y', '"+y', { desc = 'Yank to clipboard' })
vim.keymap.set('i', 'jk', '<Esc>', { desc = 'Exit insert mode' })
vim.keymap.set('n', '<leader>e', vim.diagnostic.open_float, { desc = 'Show diagnostic' })
-- Buffer-local mapping
vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = true, desc = 'LSP hover' })
-- Delete a mapping
vim.keymap.del('n', '<leader>ff')
local group = vim.api.nvim_create_augroup('MyGroup', { clear = true })
vim.api.nvim_create_autocmd('BufWritePre', {
group = group,
pattern = '*.lua',
callback = function(args)
-- args.buf, args.match, args.file
vim.lsp.buf.format({ bufnr = args.buf })
end,
})
vim.api.nvim_create_autocmd('FileType', {
group = group,
pattern = { 'javascript', 'typescript' },
callback = function()
vim.opt_local.shiftwidth = 2
end,
})
vim.api.nvim_create_autocmd('TextYankPost', {
group = group,
callback = function()
vim.hl.on_yank()
end,
})
vim.api.nvim_create_user_command('Greet', function(opts)
local name = opts.fargs[1] or 'World'
print('Hello, ' .. name .. (opts.bang and '!' or '.'))
end, {
nargs = '?',
bang = true,
desc = 'Greet someone',
complete = function()
return { 'Alice', 'Bob', 'World' }
end,
})
vim.g.mapleader = ' ' -- global variable
vim.g.maplocalleader = '\\'
vim.b.some_flag = true -- buffer variable
vim.g.loaded_netrw = 1 -- disable built-in plugin
-- ~/.config/nvim/lsp/lua_ls.lua
return {
cmd = { 'lua-language-server' },
filetypes = { 'lua' },
root_markers = { '.luarc.json', '.luarc.jsonc' },
settings = {
Lua = {
runtime = { version = 'LuaJIT' },
workspace = { library = vim.api.nvim_get_runtime_file('', true) },
},
},
}
-- init.lua
vim.lsp.enable({ 'lua_ls', 'ts_ls', 'rust_analyzer' })
-- Buffer operations
local buf = vim.api.nvim_get_current_buf()
local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
vim.api.nvim_buf_set_lines(buf, 0, -1, false, { 'new content' })
vim.api.nvim_buf_set_option(buf, 'modifiable', false)
-- Window operations
local win = vim.api.nvim_get_current_win()
vim.api.nvim_win_set_cursor(win, { 10, 0 }) -- row 10, col 0
local cursor = vim.api.nvim_win_get_cursor(win)
-- Create floating window
local buf = vim.api.nvim_create_buf(false, true)
local win = vim.api.nvim_open_win(buf, true, {
relative = 'editor',
width = 60,
height = 20,
col = 10,
row = 5,
style = 'minimal',
border = 'rounded',
})
-- Highlights
vim.api.nvim_set_hl(0, 'MyHighlight', { fg = '#ff0000', bold = true })
-- Table utilities
vim.tbl_extend('force', defaults, user_opts)
vim.tbl_deep_extend('force', defaults, user_opts)
vim.tbl_contains(list, 'value')
vim.tbl_keys(map)
vim.tbl_filter(function(v) return v > 0 end, numbers)
-- Iterators (0.10+)
vim.iter(ipairs(list)):map(function(_, v) return v * 2 end):totable()
vim.iter(pairs(map)):filter(function(k, v) return v ~= nil end):totable()
-- File system
vim.fs.find('init.lua', { upward = true })
vim.fs.root(0, { '.git', 'Makefile' })
vim.fs.joinpath(vim.fn.stdpath('config'), 'lua')
-- Scheduling (required from vim.uv callbacks)
vim.schedule(function()
vim.api.nvim_echo({ { 'Done!', 'Normal' } }, true, {})
end)
-- Deferred execution
vim.defer_fn(function()
print('Delayed message')
end, 1000)
-- Inspect
print(vim.inspect({ nested = { data = true } }))
local wezterm = require 'wezterm'
local config = wezterm.config_builder()
config.font = wezterm.font 'JetBrains Mono'
config.font_size = 14.0
config.color_scheme = 'Catppuccin Mocha'
config.window_decorations = 'RESIZE'
config.enable_tab_bar = true
config.hide_tab_bar_if_only_one_tab = true
config.window_padding = { left = 8, right = 8, top = 8, bottom = 8 }
return config
local act = wezterm.action
config.keys = {
{ key = 'l', mods = 'SUPER', action = act.ShowLauncher },
{ key = 'f', mods = 'SUPER', action = act.ToggleFullScreen },
{ key = 'd', mods = 'SUPER', action = act.SplitHorizontal { domain = 'CurrentPaneDomain' } },
{ key = 'd', mods = 'SUPER|SHIFT', action = act.SplitVertical { domain = 'CurrentPaneDomain' } },
{ key = 'w', mods = 'SUPER', action = act.CloseCurrentPane { confirm = true } },
{ key = '[', mods = 'SUPER', action = act.ActivatePaneDirection 'Prev' },
{ key = ']', mods = 'SUPER', action = act.ActivatePaneDirection 'Next' },
{ key = 'k', mods = 'SUPER', action = act.ClearScrollback 'ScrollbackAndViewport' },
{ key = 'p', mods = 'SUPER', action = act.ActivateCommandPalette },
}
wezterm.on('gui-startup', function(cmd)
local tab, pane, window = wezterm.mux.spawn_window(cmd or {})
window:gui_window():maximize()
end)
wezterm.on('format-tab-title', function(tab, tabs, panes, config, hover, max_width)
local title = tab.active_pane.title
if tab.is_active then
return { { Background = { Color = '#1e1e2e' } }, { Text = ' ' .. title .. ' ' } }
end
return ' ' .. title .. ' '
end)
-- Custom event with action_callback
config.keys = {
{
key = 'r',
mods = 'SUPER|SHIFT',
action = wezterm.action_callback(function(window, pane)
window:perform_action(act.ReloadConfiguration, pane)
end),
},
}
config.unix_domains = {
{ name = 'unix' },
}
config.ssh_domains = {
{
name = 'my-server',
remote_address = 'server.example.com',
username = 'user',
},
}
-- Default to multiplexer domain
config.default_gui_startup_args = { 'connect', 'unix' }
Detailed references in references/ directory:
Ask the user for clarification when: