Micro Editor Plugin - a Hello World Tutorial

Write your first plugin for Micro editor.

Yours truly (Tero) is the author of micro-jump plugin, and a contributor to micro-lsp. With this beginner friendly tutorial, you can create your first "hello world" plugin.

The plugin we write is very short, just nine lines of code. But here, I explain every little detail of it.

Overview

A micro plugin is just two files in a folder. Code goes to main.lua, metadata (version, homepage...) goes to repo.json.

Micro is a neat editor by itself. But there are ready-made plugins for many features, for example

  • Jump to any function or even Markdown heading (micro-jump)
  • Bookmark a line, and jump between bookmarks (micro-bookmark)
  • LSP: check and lint your code while you type, show function signatures, jump to function definition. (micro-lsp)
  • Run any command on current file (quickfix)
  • ...and many others
  • ...and maybe your new plugin

Amazing features

Amazing features? This plugin is just a simple "hello world" example.

Hellotero plugin shows a message at the bottom info bar: "Hello Micro plugin world!".

The message is shown

  • When micro is started
  • When user commands Ctrl-E "hellotero"

Prerequisites: install Micro editor

Install micro editor and try it out. In Debian (and probably Ubuntu, Kali...)

$ sudo apt-get update
$ sudo apt-get -y install micro

Then you can use it normally

$ micro hello.md

Write some text, save with Ctrl-S and quit with Ctrl-Q.

Your configuration files were automatically generated to your home directory. If you want, you can have a look with 'ls $HOME/.config/micro'.

Hello world plugin: repo.json and main.lua

Your plugin is a directory under $HOME/.config/micro/plug/. Let's create it

$ mkdir -p $HOME/.config/micro/plug/hellotero/
$ cd $HOME/.config/micro/plug/hellotero/

Minimally, two files are needed for a plugin: main.lua and repo.json. Metadata, such as name and version goes to repo.json. The code goes to main.lua. Write those two files to try it out.

$ micro repo.json
[{
  "Name": "hellotero",
  "Description": "Tutorial example - how to write Micro editor plugin",
  "Website": "https://terokarvinen.com/micro",
  "Tags": ["hello world", "tutorial", "example"],
  "Versions": [
    {
      "Version": "0.0.1",
      "Require": {
        "micro": ">=2.0.0"
      }
    }
  ]
}]
$ micro main.lua
-- Copyright 2022 Tero Karvinen http://TeroKarvinen.com
-- Tutorial example - how to write Micro editor plugin

local micro = import("micro")
local config = import("micro/config")

function init()
	config.MakeCommand("hellotero", helloteroCommand, config.NoComplete)
	helloteroCommand()
end

function helloteroCommand(bp)
	micro.InfoBar():Message("Hello Micro plugin world! See you at TeroKarvinen.com/micro")
end

Run your first Micro plugin

Just go to your home directory, open micro, and start writing a new text file tero.md.

$ cd
$ micro tero.md

On the bottom info bar, you can see the message from your new plugin: "Hello Micro plugin world! See you at TeroKarvinen.com/micro".

If you're using micro remotely (e.g. over SSH, trough vagrant...), a warning about unavailable clipboard might cover your "Hello world" message. In that case, just continue with with the "helloworld" command I'll explain next.

Write something and save your document (Ctrl-S), so that the new infobar message disappears.

Try calling your new command. Open command bar with Ctrl-E. On the bottom of the screen, a prompt ">" waits for your command. Run the hellotero command by typing it and pressing enter. Tab auto-completes.

> hellotero

Again, your message is printed on the info bar at the bottom.

Well done, you just created your first Micro plugin. Hello world!

You can start playing with your new plugin right away. If you want to know every line of it, read on.

repo.json metadata - every line explained

Repo.json contains the metadata for your plugin. Once you publish your micro editor plugin, this metadata is used by 'micro -plugin' commands.

JSON files use two data structures, key-value pair and array. These data structures are often nested.

  • object {"name": "value"} (also known as dict, hash, map, key-value pairs)
  • array ["value0", "value1", "value2"] (also known as list)
// JSON does not support comments in real life.
// A copy-pastable version of this repo.json is above.
[{
	"Name": "hellotero",
	// Use the same name for the plugin folder
	// $HOME/.config/micro/plug/hellotero/

	"Description": "Tutorial example - how to write Micro editor plugin",
	// Use the same single line explanation everywhere:
	// README.md, Gitlab/Github homepage...

	"Website": "https://terokarvinen.com/micro/",
	// A homepage or Gitlab/Github repository page, for humans

	"Tags": ["hello world", "tutorial", "example"],
	// Small number of keywords.
	// Automatically used in micro editor homepage.

	"Versions": [
		{
			"Version": "0.0.1",
			// SemVer, major.minor.patch.
			// Version can only ever go bigger.

			"Require": {
				"micro": ">=2.0.0"
				// Use small version to support distro packages.
				// Debian 11-Bullseye packages micro 2.0.8
			}
		}
	]
}]

main.lua code - every line explained

The actual code of your plugin is in main.lua. Here is the whole code of hellotero plugin before we look at it line by line, in the order it's run.

Before we dive in to each line, let's have a look at the whole nine line code:

-- Copyright 2022 Tero Karvinen http://TeroKarvinen.com
-- Tutorial example - how to write Micro editor plugin

local micro = import("micro")
local config = import("micro/config")

function init()
	config.MakeCommand("hellotero", helloteroCommand, config.NoComplete)
	helloteroCommand()
end

function helloteroCommand(bp)
	micro.InfoBar():Message("Hello Micro plugin world! See you at TeroKarvinen.com/micro")
end

Running main.lua in execution order

We'll have a look of the main.lua above, in the order it's actually run.

Comment purpose of the file

-- Copyright 2022 Tero Karvinen http://TeroKarvinen.com
-- Tutorial example - how to write Micro editor plugin

Comments are ignored by the program. They are just for humans. In Lua programming language, single line comments are marked with double dash "--", and the rest of the line is ignored.

For any code file, you should have a short explanation of the purpose of the file. You should also put your name there.

Import libraries

local micro = import("micro")
local config = import("micro/config")

We load some libraries. import("micro") loads the library and returns a value. This value is stored into new local variable we call "micro".

We will need "micro" to access the info bar at the bottom of the screen. We'll need "micro/config" to make a new command "hellotero" to be called from the Ctrl-E command bar.

init() runs first

When micro starts, it loads all plugins in plugin directory $HOME/.config/micro/plug/. When this plugin, "hellotero", is loaded, the init() function is automatically run.

function init()
	config.MakeCommand("hellotero", helloteroCommand, config.NoComplete)
	helloteroCommand()
end

The init() function runs the initital setup. Typically, it binds keyboard shortcuts, registers new commands for the Ctrl-E command bar and registers some help files for the Ctrl-E help command. Here, init() does just two things.

Register new Ctrl-E command

config.MakeCommand("hellotero", helloteroCommand, config.NoComplete)

It makes a new command "hellotero" for the command bar. User could later run this command with ctrl-E "hellotero". At this point in init(), the command is not run, just registered. The Lua function to be run if user later commands "hellotero" is helloteroCommand. That function is defined a couple of lines later.

Notice how there are no parenthesis after helloteroCommand. This is a function callback. We don't run the function helloteroCommand() just yet - because the user has not commanded that yet. We simply give a reference to helloteroCommand() to config.MakeCommand(), to be used later.

The last parameter config.NoComplete says we don't want command bar to autocomplete anything if user presses tab after "hellotero" command. For some other commands, we could as MakeCommand() to complete files, help documents, options or option values.

config.MakeCommand() comes from the imported library micro/config, which was saved to local variable config at the beginning of the program.

Run our own function

At the end of the initialization, we actually run helloteroCommand() function. This is the first time user sees something happening thanks to our plugin. The parenthesis after helloteroCommand() mean that the function is run immediately. We do this so that it's right away obvious that this hello world plugin works.

As helloteroCommand() was called, the execution jumps to that function

function helloteroCommand(bp)
	micro.InfoBar():Message("Hello Micro plugin world! See you at TeroKarvinen.com/micro")
end

The function definition specifies that helloteroCommand(bp) takes a single parameter, bp. In Lua, it seems to be possible to to call function also without parameter, as we did here.

The single line in the function calls InfoBar(). The library is micro, and it was saved to local variable also called micro at the start of the program.

InfoBar() returns an object. We than call a method, a function of that specific object. Message() method takes a single parameter, the string we want to show in the infobar. This message is shown in the infobar.

The End?

We have reached the end of the helloteroCommand() function. The execution returns to the point in init() function where we were.

But there is just one line left in init(), and it's end.

But this is not the end of the story yet.

Reacting to Events

Micro plugins use event based programming. And we have registered an event, the "hellotero" command. For a long time, nothing happens.

But then, user takes action. He presses Ctrl-E to activate the command bar. He types "hellotero" and presses enter. As we registered this command earlier with config.MakeCommand(), micro now knows to run our function helloteroCommand().

Function helloteroCommand() works just like before. The message "Hello Micro plugin..." is shown at the bottom, in the info bar.

As long as micro is open, the plugin keeps reacting to events. In this case, our hellotero plugin is ready to react if user again presses Ctrl-E and types "hellotero" to command bar.

Try it out!

Whoe! I always say that "hello world" is one of the toughest programs to write - or read.

Try copy pasting my examples to your Micro plugin folder. Try it out. Make some changes.

Bonus: feeling advanced? Have a look at the sources of micro-jump and other plugins. Then apply some of the ideas to your own plugins.

Next >> Learn more micro.

Adminstrivia

Copyright Tero Karvinen, all rights reserved. The two code examples "main.lua" and "repo.json" are Free to use under the MIT license, so you can just copy paste them to get started.

Tested with Debian 11-Bullseye.

This article has been updated after publishing.