Plugin for Neovim
Ceramicist is Neovim plugin to execute shell commands (like
:!) and display their output in a dedicated window. It is
similar to the terminal
available in Neovim, but with some features intended to make it more
convenient to use when you don’t need a full shell.
The main features are:
The shell command to run is written directly in Neovim’s command line.
Multiple commands can be appended to a single session.
The terminal window automatically starts Terminal mode when a shell command is run, and returns to Normal mode when it finishes.
A header is written to show which shell command is executed.
A footer is written to show the exit status and the duration of the command.
The statusline shows information about the session.
Ceramicist can be installed with any package manager for Neovim, like
vim.pack:
vim.pack.add { "https://github.com/ayosec/nvim-ceramicist" }
require("ceramicist").setup({})The setup() function creates an instance of
Ceramicist.
require("ceramicist").setup({})This instance is accessible through the user-command defined in the
setup() options. By default, the user-command is
:Ceramicist:
:Ceramicist ./scripts/somethingThe arguments of the user-command specify the shell command to run,
like :!.
If there is another job running when a new one is executed, the previous job is interrupted before starting the new one.
To define a different command name (for example,
:C):
require("ceramicist").setup {
user_command = { name = "C" }
}Then:
:C make testIf the user-command is executed with no arguments it repeats the last
shell command, like :!!.
:Ceramicist make test
:Ceramicist " Repeat 'make test'You can also use the rerun() function, available in the
buffer variable b:ceramicist_session, to repeat it.
The next example uses an autocommand to trigger a rerun when C-R is pressed on a window for a Ceramicist session:
vim.api.nvim_create_autocmd("User", {
pattern = "Ceramicist/SessionCreated",
callback = function(args)
local map_opts = { buf = args.data.buffer }
vim.keymap.set(
"n", "<C-R>",
function() vim.b.ceramicist_session().rerun() end,
map_opts
)
end
})The :CeramicistToggleWatch user-command can enable or
disable the Watch mode in a session. The user-command changes only the
session in the current buffer. To enable Watch mode in multiple
sessions, execute it in each one.
When Watch mode is enabled, the last shell command in the session is
repeated after saving changes on any buffer. See
:h BufWritePost.
If the user-command is executed with the ! modifier (see
:h :_!) the output from previous jobs is removed before
running a new one:
:Ceramicist scripts/tests " The output is added to the session.
:Ceramicist! scripts/tests " Reset session before running the job.Multiple sessions of the same instance can be created by adding a number to the user-command, either before or after the name:
:Ceramicist scripts/something " Default session.
:2Ceramicist scripts/other " Session 2
:Ceramicist2 scripts/other " Session 2The number is the session id. Its default value depends on the context where the user-command is executed:
1.Multiple instances of Ceramicist can be created with different calls
to setup(), each one with its own user-command name.
The next example defines an instance on :C with the
default configuration, and another instance on :M that
executes make(1) instead of a shell expression. It also
provides a custom completion list for :M:
require("ceramicist").setup {
user_command = { name = "C" }
}
require("ceramicist").setup {
user_command = {
name = "M",
complete = function()
return { "all", "fmt", "test" }
end,
},
job_command = function(cmdline)
return { "make", cmdline }
end,
}For more details on the user_command.complete field, see
:h :command-complete and
:h :command-completion-customlist.
By default, the window for the session is open in a horizontal split
at the bottom of the current tab. The modifiers :vertical,
:tab, :topleft, etc, can be used to change the
placement of the new window:
:topleft Ceramicist ... open a horizontal split at the
top.:vertical Ceramicist ... open a vertical split to the
right.:vertical topleft Ceramicist ... open a vertical split
to the left.:tab Ceramicist ... open the session in a new tab.A session can be deleted with :bdelete! (or just
:bd!). When it is deleted, its state (the output from
previous jobs, the last shell command, etc) is lost.
If there is a running job when the buffer is deleted, the job is interrupted.
The session will be recreated the next time the user-command is executed.
The function list_sessions returns an iterator to get
all active sessions. These objects can be used to add extra
functionality to manage the sessions.
For example, to add a :DeleteCeramicistSessions
user-command to delete all idle (i.e. without a running job)
sessions:
vim.api.nvim_create_user_command(
"DeleteCeramicistSessions",
function()
for session in require("ceramicist").list_sessions() do
if not session.is_running() then
vim.cmd.bdelete { session.buffer, bang = true }
end
end
end,
{ desc = "Delete idle Ceramicist sessions" }
)The default configuration provided by Ceramicist is very basic, since it is expected that users adjust it to their own preferences.
See config.lua
for a reference of all available options.
The configuration is defined as the argument of the
setup() function. Each call to setup() creates
a new Ceramicist instance. There is no global state, so each instance
has its own configuration.
The fields output.header and output.footer
can define functions that are used to render the header before running a
job, and the footer after it is finished.
Both functions must return a list of { text, highlight }
pairs, like the virt_text field of
:h nvim_buf_set_extmark.
header() receives a single argument, a string with the
command line used to run the job.
footer() receives the exit code and the duration in
nanoseconds. The exit code can be either an integer, or a string with
the name of the signal that stopped the process.
The field statusline defines a string to be used as the
statusline of the sessions of the instance.
The buffer variable b:ceramicist_statusline is a
function to provide a short summary of the status of the session (the
PID of the running job, if Watch mode is enabled, the full command line,
etc).
If statusline is false, the statusline is
not modified.
Extra configuration options can be set by using autocommands:
Ceramicist/SessionCreatedCeramicist/JobFinishedCeramicist/JobStartedIn the data field, SessionCreated receives
the buffer number, the window id, and the instance of the session.
For JobStarted and JobFinished, they also
receive the command line to run the job.
The next example uses SessionCreated to add 2 key
mappings (C-R to repeat the last command, and C-A
to toggle Watch mode).
vim.api.nvim_create_autocmd("User", {
pattern = "Ceramicist/SessionCreated",
callback = function(args)
local map_opts = { buf = args.data.buffer }
-- <C-A> toggle watch
vim.keymap.set("n", "<C-A>", "<Cmd>CeramicistToggleWatch<CR>", map_opts)
-- <C-R> rerun
vim.keymap.set(
"n", "<C-R>",
function() vim.b.ceramicist_session().rerun() end,
map_opts
)
end
})The next example uses matchadd()
to highlight some patterns when the shell command starts with
scripts/.
vim.api.nvim_create_autocmd("User", {
pattern = "Ceramicist/JobStarted",
callback = function(args)
local buffer = args.data.buffer
local window = args.data.window
local cmdline = args.data.cmdline
-- Use a buffer variable to track if the matchadd() is done.
if vim.b[buffer].has_matchadd then return end
if vim.startswith(cmdline, "scripts/") then
vim.b[buffer].has_matchadd = true
vim.fn.matchadd("DiagnosticInfo", "^INFO", 0, -1, { window = window })
vim.fn.matchadd("DiagnosticError", "^ERROR", 0, -1, { window = window })
end
end
})The configuration example below:
vim.api.nvim_set_hl(0, "CeramicistNormal", { bg = "#001429" })
vim.api.nvim_set_hl(0, "CeramicistHeader", { bg = "#002E5C" })
vim.api.nvim_set_hl(0, "Ceramicist.Time", { fg = "#7777FF" })
vim.api.nvim_set_hl(0, "CeramicistFooterFail", { bg = "#652131" })
vim.api.nvim_set_hl(0, "CeramicistFooterSuccess", { bg = "#21653F" })
vim.api.nvim_set_hl(0, "Ceramicist.Success.Duration", { bg = "#329A5F" })
vim.api.nvim_set_hl(0, "Ceramicist.Fail.Duration", { bg = "#9A324A" })
vim.api.nvim_set_hl(0, "Ceramicist.Fail.Code", { bg = "#732638" })
vim.api.nvim_set_hl(0, "Ceramicist.Fail.Signal", { bg = "#73264D" })
vim.api.nvim_set_hl(0, "CeramicistStatusLineWatch", { bg = "#004D4D" })
vim.api.nvim_set_hl(0, "CeramicistStatusLineRunning", { bg = "#702000" })
local utils = require("ceramicist.utils")
local context = require("ceramicist").setup({
user_command = { name = "C" },
output = {
gap = 3,
header = function(cmdline)
return {
{ vim.fn.strftime(" %H:%M:%S "), "Ceramicist.Time" },
{ utils.escape_control_chars(cmdline), "" },
}
end,
footer = function(exit, duration)
local fd = " " .. utils.format_duration(duration) .. " "
if exit == 0 then
return { { " ", "" }, { fd, "Ceramicist.Success.Duration" } }
end
-- Failed
local status
if type(exit) == "number" then
status = { " $! = " .. exit .. " ", "Ceramicist.Fail.Code" }
else
status = { " " .. exit .. " ", "Ceramicist.Fail.Signal" }
end
local sep = { " ", "" }
return { sep, status, sep, { fd, "Ceramicist.Fail.Duration" } }
end,
},
})
vim.api.nvim_create_autocmd("User", {
pattern = "Ceramicist/SessionCreated",
callback = function(args)
local map_opts = { buf = args.data.buffer }
-- <C-A> toggle watch
vim.keymap.set(
"n", "<C-A>",
"<Cmd>CeramicistToggleWatch<CR>",
map_opts
)
-- <C-R> rerun
vim.keymap.set(
"n", "<C-R>",
function() vim.b.ceramicist_session().rerun() end,
map_opts
)
end
})There are many plugins providing a similar functionality to Ceramicist. This is a non-exhaustive list of alternatives: