This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch tytab
in repository terminology.
View the commit online.
commit 2e64a40b4afd455f41a79255ade9e198324befe9
Author: [email protected] <[email protected]>
AuthorDate: Sat Mar 28 14:09:03 2026 -0600
feat: add tytab CLI tool for tab and split management
Provides a user-friendly command-line interface to the escape
sequences for named panel targeting and tab creation.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
src/bin/meson.build | 6 ++
src/bin/tytab.c | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 175 insertions(+)
diff --git a/src/bin/meson.build b/src/bin/meson.build
index f12f5d7a..b91324f5 100644
--- a/src/bin/meson.build
+++ b/src/bin/meson.build
@@ -48,6 +48,7 @@ tyq_sources = ['tycommon.c', 'tycommon.h', 'tyq.c']
tycat_sources = ['tycommon.c', 'tycommon.h', 'tycat.c', 'extns.c', 'extns.h']
tyls_sources = ['extns.c', 'extns.h', 'tyls.c', 'tycommon.c', 'tycommon.h']
tysend_sources = ['tycommon.c', 'tycommon.h', 'tysend.c']
+tytab_sources = ['tycommon.c', 'tycommon.h', 'tytab.c']
tyfuzz_sources = ['termptyesc.c', 'termptyesc.h',
'backlog.c', 'backlog.h',
'termptyops.c', 'termptyops.h',
@@ -127,6 +128,11 @@ executable('tysend',
install: true,
include_directories: config_dir,
dependencies: terminology_dependencies)
+executable('tytab',
+ tytab_sources,
+ install: true,
+ include_directories: config_dir,
+ dependencies: terminology_dependencies)
if fuzzing
executable('tyfuzz',
diff --git a/src/bin/tytab.c b/src/bin/tytab.c
new file mode 100644
index 00000000..a9365c3e
--- /dev/null
+++ b/src/bin/tytab.c
@@ -0,0 +1,169 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "private.h"
+#include "tycommon.h"
+
+static void
+print_usage(const char *argv0)
+{
+ printf("Usage: %s " HELP_ARGUMENT_SHORT " name [NAME]\n"
+ " %s " HELP_ARGUMENT_SHORT " new [NAME DIR] [-- CMD...]\n"
+ "\n"
+ " Manage tabs and splits in Terminology.\n"
+ "\n"
+ " Commands:\n"
+ " name [NAME] Set panel name (omit NAME to clear)\n"
+ " new [NAME DIR] [-- CMD] Create a new tab\n"
+ "\n"
+ " NAME Target panel name\n"
+ " DIR Split direction: left, right, top, bottom (or l, r, t, b)\n"
+ " CMD Command to run (default: shell)\n"
+ "\n"
+ HELP_ARGUMENT_DOC "\n"
+ "\n",
+ argv0, argv0);
+}
+
+static char
+_parse_direction(const char *s)
+{
+ if (!strcmp(s, "l") || !strcmp(s, "left")) return 'l';
+ if (!strcmp(s, "r") || !strcmp(s, "right")) return 'r';
+ if (!strcmp(s, "t") || !strcmp(s, "top")) return 't';
+ if (!strcmp(s, "b") || !strcmp(s, "bottom")) return 'b';
+ return 0;
+}
+
+/* Find index of "--" in argv, or return -1 */
+static int
+_find_separator(int argc, char **argv, int start)
+{
+ int i;
+ for (i = start; i < argc; i++)
+ {
+ if (!strcmp(argv[i], "--"))
+ return i;
+ }
+ return -1;
+}
+
+/* Join argv[start..argc-1] with spaces into buf */
+static void
+_join_args(char *buf, size_t bufsz, int argc, char **argv, int start)
+{
+ size_t pos = 0;
+ int i;
+ for (i = start; i < argc && pos < bufsz - 1; i++)
+ {
+ if (i > start && pos < bufsz - 1)
+ buf[pos++] = ' ';
+ size_t len = strlen(argv[i]);
+ if (pos + len >= bufsz) len = bufsz - 1 - pos;
+ memcpy(buf + pos, argv[i], len);
+ pos += len;
+ }
+ buf[pos] = '\0';
+}
+
+int
+main(int argc, char **argv)
+{
+ char tbuf[8192];
+
+ ON_NOT_RUNNING_IN_TERMINOLOGY_EXIT_1();
+ ARGUMENT_ENTRY_CHECK(argc, argv, print_usage);
+
+ if (argc < 2)
+ {
+ print_usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (!strcmp(argv[1], "name"))
+ {
+ /* tytab name [NAME] */
+ if (argc >= 3)
+ {
+ if (strchr(argv[2], ';'))
+ {
+ fprintf(stderr, "NAME must not contain ';'\n");
+ return EXIT_FAILURE;
+ }
+ snprintf(tbuf, sizeof(tbuf), "%c}tn;%s", 0x1b, argv[2]);
+ }
+ else
+ snprintf(tbuf, sizeof(tbuf), "%c}tn;", 0x1b);
+
+ if (ty_write(1, tbuf, strlen(tbuf) + 1) != (signed)(strlen(tbuf) + 1))
+ perror("write");
+ }
+ else if (!strcmp(argv[1], "new"))
+ {
+ /* tytab new [NAME DIR] [-- CMD...] */
+ int sep = _find_separator(argc, argv, 2);
+ int positional_end = (sep >= 0) ? sep : argc;
+ int positional_count = positional_end - 2;
+ const char *name = NULL;
+ char dir = 0;
+ char cmdbuf[4096];
+
+ cmdbuf[0] = '\0';
+
+ if (positional_count >= 2)
+ {
+ /* NAME and DIR provided */
+ name = argv[2];
+ if (strchr(name, ';'))
+ {
+ fprintf(stderr, "NAME must not contain ';'\n");
+ return EXIT_FAILURE;
+ }
+ dir = _parse_direction(argv[3]);
+ if (!dir)
+ {
+ fprintf(stderr, "invalid direction: %s\n", argv[3]);
+ return EXIT_FAILURE;
+ }
+ }
+ else if (positional_count == 1)
+ {
+ /* NAME without DIR is ambiguous — require both */
+ fprintf(stderr, "NAME requires a DIR argument\n");
+ print_usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+ /* else: positional_count == 0, no name, no dir */
+
+ /* Build CMD from args after -- */
+ if (sep >= 0 && sep + 1 < argc)
+ _join_args(cmdbuf, sizeof(cmdbuf), argc, argv, sep + 1);
+
+ /* Format escape: tt[;NAME;DIR[;CMD]] */
+ if (name && dir)
+ {
+ if (cmdbuf[0])
+ snprintf(tbuf, sizeof(tbuf), "%c}tt;%s;%c;%s", 0x1b, name, dir, cmdbuf);
+ else
+ snprintf(tbuf, sizeof(tbuf), "%c}tt;%s;%c", 0x1b, name, dir);
+ }
+ else
+ {
+ if (cmdbuf[0])
+ snprintf(tbuf, sizeof(tbuf), "%c}tt;;;%s", 0x1b, cmdbuf);
+ else
+ snprintf(tbuf, sizeof(tbuf), "%c}tt", 0x1b);
+ }
+
+ if (ty_write(1, tbuf, strlen(tbuf) + 1) != (signed)(strlen(tbuf) + 1))
+ perror("write");
+ }
+ else
+ {
+ fprintf(stderr, "unknown command: %s\n", argv[1]);
+ print_usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.