Pepper Editor

A simple and opinionated modal code editor for your terminal

View the Project on GitHub

Here you’ll find snippets for common pepper solutions.

If you want to see a full example config folder for pepper, check my config repository.

load config file on startup

Since pepper does not try to load config files from a specific folder at startup, the best way to emulate this is by creating an alias in your shell profile.

alias pp='pepper --config ~/.config/pepper/init.pp'

With this, whenever you type pp, pepper will start with sourcing the configs you put inside the file ~/.config/pepper/init.pp. This is better because you’re in control over not only when pepper loads configs from the disk but also from where it fetches them.

project config

It’s also easy to load, say, configs that are per project. If you determine that all of your projects that you wish to configure individually, will do so through a file named project.pp in its root, you can set your pepper alias:

alias pp='pepper --config ~/.config/pepper/init.pp --try-config project.pp'

The only difference from --try-config to --config is that it won’t report an error if the config file was not found.

Both --config and --try-config are repeatable and can be used to load configs from files in different locations. The files are sourced in the order they appear in the command line.

multi-file config

It’s possible to recursively load config files from a config file by using the source command.

# in your `init.pp`

source "other-config.pp"
source "some-other-config.pp"


You can remap keys with the keymap command.

With this, you can hit <c-s> to save a buffer to a file:

# The `<space>` after `:` makes it so the `save` command is not added to the command history
map -normal <c-s> :<space>save<enter>

If you wish to see all the keybindings that are created by default, you can see the builtin default config.

run lsp server automatically

An LSP server is usually started when a file it should handle is opened, normally known from its extension. By using the lsp command, it’s possible to automatically start an LSP server like that. For each LSP server you wish to register, add this to one of your config files:

lsp "**.ext" "lsp-server-command"

Where "**.ext" is a glob pattern that, when matched against a buffer just opened, will invoke the LSP server using "lsp-server-command". In this case, whenever we open a buffer with extension .ext.

If you wish to inspect/debug the protocol messages, you can instead register it as

lsp "**.ext" "lsp-server-command" -log=my-lsp-server-log

Where you can substitute my-lsp-server-log with any buffer name you want.

You can check a full example with many LSP server configured in my my config repository.

run program with !

While in normal mode, you’ll be able to enter ‘run program’ mode by pressing !. Its output will be printed to the status bar.

macro run-shell {
	read-line -prompt="!" {
		# this block executes once read-line finishes,
		# and register %z will contain the line read

		spawn %z { # spawn the typed command
			# this block executes once the process finishes,
			# its stdout will also be placed on register %z

			print %z # print process output
map -normal ! :<space>run-shell<enter> # bind run-shell macro to `!`

simple fuzzy file opener

This uses fd to feed file names to the picker ui which then lets you choose a file to open. While in normal mode, you can invoke it with <c-o>.

macro fuzzy-open-file {
	spawn "fd -tf -0 --path-separator / ." -split-on-byte=0 {
		# this block executes whenever `fd` returns a new entry (separated by byte 0)
		# those stdout bytes are placed in register %z

		add-picker-option %z # as entries are found, populate the picker ui
	# open the picker ui with the prompt "open:"
	pick -prompt="open:" {
		# this block executes once the file is chosen,
		# and register %z will contain its path
		open %z # open the chosen file
map -normal <c-o> :<space>fuzzy-open-file<enter> # bind fuzzy-open-file macro to `<c-o>`

simple grep

This defines a macro command that will invoke ripgrep and then display its results in a new buffer from where you can jump to the found locations.

You can use it like :rg MyStruct and a buffer will open with all the results. Then you can use pepper’s builtin gf to jump to a filepath under the cursor.

macro rg z {
	# when this macro is invoked, this block executes and
	# the register %z will contain its argument

	# open a temp buffer named "rg-find-results.refs"
	# the ".refs" extension is useful for syntax highlighting
	open -no-history -no-save -no-word-database "rg-find-results.refs"
	# delete buffer contents
	execute-keys <esc>aad
	# insert text from `rg` stdout when searching for the pattern
	# given as argument to this macro (register %z)
	replace-with-output -split-on-byte=10 "rg --line-number --path-separator / --no-ignore-global %z"

NOTE: you also use the flag -auto-close for the open command. This will automatically close the ripgrep results buffer once you jump out of it.

simple buffer format

This command will save the current buffer, then call rustfmt with its path as argument. Once rustfmt returns, it reloads the buffer contents from file to apply the formatting. The ff keybind will trigger the command while in normal mode.

macro format {
	save # save buffer to make sure all changes go to the file system
	%z = buffer-path # save the current buffer path to register %z
	# spawn `rustfmt` passing it the current buffer path
	spawn "rustfmt %z" {
		reload # once rustfmt finishes, reload contents from the file system
map -normal ff :<space>format<enter> # bind format macro to `ff`

NOTE: this command may be most useful when defined from a project config since you probably want to use a different formatter per project. Also, since you’re reloadin the buffer contents, you’ll lose the buffer’s history.

vim bindings

These mappings somewhat emulate basic vanilla vim keybindings. However please take note that this will not correctly emulate vim’s visual mode, some builtin features may become inaccessible without further tweakings and, obviously, the experience will not be the same of using vim.

If you need 100% vim compatibility, simply use vim.

For more details, check the builtin keybindigs.

map -normal gg gk
map -normal G gj

map -normal $ gl
map -normal ^ gi
map -normal 0 gh

map -normal <c-o> <c-p>
map -normal <c-i> <c-n>

map -normal f ]]
map -normal F [[
map -normal t ][
map -normal T []
map -normal ; }
map -normal , {

map -normal { <c-k>
map -normal } <c-j>

map -normal / s
map -normal ? s
map -normal N p
map -normal * Nn
map -normal # Pp

map -normal a li
map -normal A gli
map -normal <c-r> U

map -normal p Y

map -normal zt zk
map -normal zb zj

map -normal ys y
map -normal yy Vy
map -normal yiw Awy<esc>
map -normal yaw awy<esc>
map -normal yi( a(y<esc>
map -normal ya( A(y<esc>
map -normal yi[ a[y<esc>
map -normal ya[ A[y<esc>
map -normal yi{ a{y<esc>
map -normal ya{ A{y<esc>
map -normal yi< a<y<esc>
map -normal ya< A<y<esc>
map -normal yi" a"y<esc>
map -normal ya" A"y<esc>
map -normal yi' a'y<esc>
map -normal ya' A'y<esc>
map -normal yi` a`y<esc>
map -normal ya` A`y<esc>

map -normal ds d
map -normal dd Vd
map -normal diw Awd
map -normal daw awd
map -normal di( a(d
map -normal da( A(d
map -normal di[ a[d
map -normal da[ A[d
map -normal di{ a{d
map -normal da{ A{d
map -normal di< a<d
map -normal da< A<d
map -normal di" a"d
map -normal da" A"d
map -normal di' a'd
map -normal da' A'd
map -normal di` a`d
map -normal da` A`d

map -normal cs i
map -normal cc ci
map -normal ciw Awi
map -normal caw awi
map -normal ci( a(i
map -normal ca( A(i
map -normal ci[ a[i
map -normal ca[ A[i
map -normal ci{ a{i
map -normal ca{ A{i
map -normal ci< a<i
map -normal ca< A<i
map -normal ci" a"i
map -normal ca" A"i
map -normal ci' a'i
map -normal ca' A'i
map -normal ci` a`i
map -normal ca` A`i