tmux's "object oriented" approach to commands
2023-01-03 00:00:00 +0000 UTCI 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 session
s and struct window
s.
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.