tmux's "object oriented" approach to commands

2023-01-03 00:00:00 +0000 UTC

I recently became curious about tmux’s implementation of commands. According to this talk tmux uses an “object-oriented” approach in this area.

I wanted to know more about this “object-oriented approach” was designed in a C program. Below is a high-level overview of the structure of a tmux command.

Anatomy of a Command

The structure that represents an instance of a command in tmux is struct cmd:

/* Instance of a command. */
struct cmd {
	const struct cmd_entry	 *entry;
	struct args		 *args;
	u_int			  group;

	char			 *file;
	u_int			  line;

	TAILQ_ENTRY(cmd)	  qentry;
};
TAILQ_HEAD(cmds, cmd);

This structure consists of a pointer to a command descriptor of type struct cmd_entry, as well as some execution context. It also makes use of the sys/queue.h metaprogrammed data structure TAILQ.

As an example, let’s look into the command kill-window Here is the man section:

kill-window [-a] [-t target-window] (alias: killw) Kill the current window or the window at target-window, removing it from any sessions to which it is linked. The -a option kills all but the window given with -t.

The command descriptor

tmux command descriptors are of type struct cmd_entry. This is the descriptor for the kill-window command:

const struct cmd_entry cmd_kill_window_entry = {
	.name = "kill-window",
	.alias = "killw",

	.args = { "at:", 0, 0, NULL },
	.usage = "[-a] " CMD_TARGET_WINDOW_USAGE,

	.target = { 't', CMD_FIND_WINDOW, 0 },

	.flags = 0,
	.exec = cmd_kill_window_exec
};

The fields name and alias are simple enough. These are the two strings used to identify the command. This tells us that running tmux kill-window and tmux killw will both resolve to the same command internally. name, alias, usage, and flags are all “member variables” in the object-oriented idiom. args and target are similar but hold a struct value.

These member variables contain information about parsing command arguments, selecting the target structure for the command, and other potentially necessary state.

The exec field is key – exec is a function pointer for the function handling command execution. In this case, cmd_kill_window_exec. This defines a “member function” or “method” in the OO idiom.

The exec function

Let’s look closer at the execution function. The signature for a command function will look something like this:

enum cmd_retval	 exec(struct cmd *self, struct cmdq_item *item);

The return type enum cmd_retval is an enumerated set of command results. The two arguments, struct pointers of type struct cmd* and struct cmdq_item*, provide the execution context for the command.

Here is a minimal edit of the kill-window command exec function:

static enum cmd_retval
cmd_kill_window_exec(struct cmd *self, struct cmdq_item *item)
{
	struct args		*args = cmd_get_args(self);
	struct cmd_find_state	*target = cmdq_get_target(item);
	struct winlink		*wl = target->wl, *loop;

	/* ... omitted ... */

	server_kill_window(wl->window, 1);
	return (CMD_RETURN_NORMAL);
}

We can see that the first three lines of the function are extracting necessary execution context from the parameters.

The function parameters are struct cmd *self and struct cmdq_item *item. The first parameter makes more clear what is meant by an “object-oriented approach.” This function is semantically similar to a method call on an object in an object-oriented language.

Our struct cmd *self is operating much like the this pointer used in C++. Here is a similar example in C++ pseudocode:

class KillWindowCmd : public Cmd {
public:
	enum cmd_retval exec(CmdqItem *item) {
		Args args = this->args;
		//...
	}
}

In fact, the use of the exec function is similar to an overloaded method call. Let’s look at the place this function is invoked:

static enum cmd_retval
cmdq_fire_command(struct cmdq_item *item)
{
	/* ... omitted ... */	
	struct cmd *cmd = item->cmd;
	struct args *args = cmd_get_args(cmd);
	const struct cmd_entry *entry = cmd_get_entry(cmd);
	/* ... omitted ... */

	retval = entry->exec(cmd, item);

	/* ... omitted ... */
}

Recall the definition of the exec member of the cmd_entry struct:

enum cmd_retval	 (*exec)(struct cmd *, struct cmdq_item *);

In effect, each struct cmd_entry defines the type of a command. So a tmux struct cmd behaves differently based on the cmd_entry it contains. In an object oriented language, similar semantics could be achieved by defining an abstract class or interface cmd with concrete classes for each command type.

Again C++ pseudocode is illustrative:

class Cmd {
	public:
		virtual enum cmd_retval exec(CmdqItem *item) = 0;
	protected:
		Args *args;
}

class KillWindowCmd : public Cmd {
	public:
		enum cmd_retval exec(CmdqItem *item)
		{
			Args args = this->args;
			// ...
		}
}

Where in the existing C tmux implementation, every command “object” is of type struct cmd*, in the above C++ code, each command would be represented by its own concrete class implementing the Cmd abstract class. Then the need for a separate set of static cmd_entry command descriptors which provide the overloaded exec functions is removed by the first-class object-oriented features of this pseudo-C++.

Said another way, we can see by this example that an object of type struct cmd is similar to an instance of the abstract class Cmd. Having a non-null entry pointer to a struct cmd_entry concretizes the object by implementing the virtual exec function.

Conclusion

tmux uses an “object-oriented” approach to handling commands. In practice, this means the use of a function pointer to mimic the semantics of method overloading when an abstract class is implemented by a concrete class. This is supported by neat encapsulation of the necessary state in containing structs and a static set of command descriptors that provide the overloaded exec-functions.

Appendix: server_kill_window

The actual work of killing the window is dispatched to a server function. The details of this function are out of scope for this investigation, but some highlights are interesting:

/* cmd-kill-window.c */
server_kill_window(wl->window, 1);

/* server-fn.c */
void
server_kill_window(struct window *w, int renumber)
{
	struct session	*s, *s1;
	struct winlink	*wl;

	RB_FOREACH_SAFE(s, sessions, &sessions, s1) {
		if (!session_has(s, w))
			continue;

		server_unzoom_window(w);
		while ((wl = winlink_find_by_window(&s->windows, w)) != NULL) {
			if (session_detach(s, wl)) {
				server_destroy_session_group(s);
				break;
			} else
				server_redraw_session_group(s);
		}

		if (renumber)
			server_renumber_session(s);
	}
	recalculate_sizes();
}

Specfically, note the use of the RB_FOREACH_SAFE macro. This macro is sourced from the sys/tree.h library. This control flow macro iterates over a red-black tree (in this case, the value sessions). The _SAFE postfix indicates that deletion of a node and free()ing a node are safe within the loop.

The interior while loop and if statement within the loop are notable and idiomatic: the call of session_detach, a function with side effects, as a branching condition drives each iteration of the loop.

This function relies on the struct winlink at its core – a struct that (among other things) performs a role akin to a record in a join table between struct sessions and struct windows.

Finally, as mentioned before, this function makes use of a global state variable “sessions.” This emphasizes that the command architecture is an object-oriented approach within a procedural language.

Tags: c tmux tmux-internals