diff --git a/bindings.h b/bindings.h index 1819a08..ce07811 100644 --- a/bindings.h +++ b/bindings.h @@ -24,6 +24,7 @@ typedef struct binding { INT bi_code; tkey_t *bi_key; function_t *bi_func; + WCHAR *bi_macro; TTS_TAILQ_ENTRY(binding) bi_entries; } binding_t; @@ -32,6 +33,6 @@ typedef TTS_TAILQ_HEAD(bindlist, binding) binding_list_t; extern binding_list_t bindings; tkey_t *find_key(const WCHAR *name); -void bind_key(const WCHAR *key, const WCHAR *func); +void bind_key(const WCHAR *key, const WCHAR *func, int is_macro); #endif /* !TTS_BINDINGS_H */ diff --git a/commands.c b/commands.c index 906cabc..4696625 100644 --- a/commands.c +++ b/commands.c @@ -15,6 +15,7 @@ static command_t commands[] = { { WIDE("bind"), c_bind }, + { WIDE("macro"), c_macro }, { WIDE("style"), c_style }, { WIDE("set"), c_set }, { } @@ -80,7 +81,20 @@ c_bind(argc, argv) return; } - bind_key(argv[1], argv[2]); + bind_key(argv[1], argv[2], 0); +} + +void +c_macro(argc, argv) + size_t argc; + WCHAR **argv; +{ + if (argc != 3) { + cmderr(WIDE("Usage: macro ")); + return; + } + + bind_key(argv[1], argv[2], 1); } void diff --git a/commands.h b/commands.h index cad450b..22b1085 100644 --- a/commands.h +++ b/commands.h @@ -25,6 +25,7 @@ command_t *find_command(const WCHAR *); void c_bind (size_t, WCHAR **); void c_style (size_t, WCHAR **); void c_set (size_t, WCHAR **); +void c_macro (size_t, WCHAR **); void cmderr (const WCHAR *, ...); void vcmderr (const WCHAR *, va_list); diff --git a/functions.c b/functions.c index 57c91a0..c1ed4c6 100644 --- a/functions.c +++ b/functions.c @@ -592,14 +592,20 @@ binding_t *bi; i = 0; TTS_TAILQ_FOREACH(bi, &bindings, bi_entries) { - WCHAR s[128]; + WCHAR s[128], t[16]; + if (bi->bi_key) - SNPRINTF(s, WSIZEOF(s), WIDE("%-10"FMT_L"s %"FMT_L"s (%"FMT_L"s)"), - bi->bi_key->ky_name, bi->bi_func->fn_desc, - bi->bi_func->fn_name); + SNPRINTF(t, WSIZEOF(t), WIDE("%"FMT_L"s"), bi->bi_key->ky_name); else - SNPRINTF(s, WSIZEOF(s), WIDE("%"FMT_L"c %"FMT_L"s (%"FMT_L"s)"), - bi->bi_code, bi->bi_func->fn_desc, bi->bi_func->fn_name); + SNPRINTF(t, WSIZEOF(t), WIDE("%"FMT_L"c"), bi->bi_code); + + if (bi->bi_macro) + SNPRINTF(s, WSIZEOF(s), WIDE("%-10"FMT_L"s execute macro: %"FMT_L"s"), + t, bi->bi_macro); + else + SNPRINTF(s, WSIZEOF(s), WIDE("%-10"FMT_L"s %"FMT_L"s (%"FMT_L"s)"), + t, bi->bi_func->fn_desc, bi->bi_func->fn_name); + help[i] = STRDUP(s); i++; } diff --git a/str.c b/str.c index 4b89814..a489a91 100644 --- a/str.c +++ b/str.c @@ -17,6 +17,7 @@ tokenise(str, res) { int ntoks = 0; const WCHAR *p, *q; +WCHAR *r; *res = NULL; p = str; @@ -24,6 +25,7 @@ const WCHAR *p, *q; for (;;) { ptrdiff_t sz; int qskip = 0; + int isbsl = 0; /* Skip leading whitespace */ while (ISSPACE(*p)) @@ -37,14 +39,13 @@ const WCHAR *p, *q; if (*q == '"') { /* Quoted string - scan for end of string */ - int isbsl = 0; p++; while (*++q) { /* Handle escaping with backslash; currently works but the \ isn't * removed from the string. */ - if (*q == '\\') { + if (!isbsl && (*q == '\\')) { isbsl = 1; continue; } @@ -72,6 +73,33 @@ const WCHAR *p, *q; *res = realloc(*res, sizeof(WCHAR *) * (ntoks + 1)); (*res)[ntoks] = malloc(sizeof(WCHAR) * (sz + 1)); MEMCPY((*res)[ntoks], p, sz); + + /* Handle \ escapes */ + for (r = (*res)[ntoks]; r < ((*res)[ntoks] + sz);) { + if (!isbsl) { + if (*r == '\\') { + MEMMOVE(r, r + 1, sz - (r - (*res)[ntoks])); + sz--; + isbsl = 1; + continue; + } + + r++; + continue; + } + + switch (*r) { + case 't': *r = '\t'; break; + case 'n': *r = '\n'; break; + case 'r': *r = '\r'; break; + case 'v': *r = '\v'; break; + case '\\': *r = '\\'; break; + } + + isbsl = 0; + r++; + } + (*res)[ntoks][sz] = 0; ntoks++; diff --git a/tts.c b/tts.c index 25e9aff..dc74e9d 100644 --- a/tts.c +++ b/tts.c @@ -150,6 +150,8 @@ entry_t *curent; int showinv = 0; +static WCHAR *macro_text, *macro_pos; + static attrname_t attrnames[] = { { WIDE("normal"), WA_NORMAL }, { WIDE("bold"), WA_BOLD }, @@ -401,32 +403,32 @@ struct kevent evs[2], rev; curs_set(0); - bind_key(WIDE("?"), WIDE("help")); - bind_key(WIDE("a"), WIDE("add")); - bind_key(WIDE("A"), WIDE("add-old")); - bind_key(WIDE("d"), WIDE("delete")); - bind_key(WIDE("u"), WIDE("undelete")); - bind_key(WIDE("q"), WIDE("quit")); - bind_key(WIDE(""), WIDE("quit")); - bind_key(WIDE("i"), WIDE("invoice")); - bind_key(WIDE("b"), WIDE("billable")); - bind_key(WIDE("m"), WIDE("mark")); - bind_key(WIDE("U"), WIDE("unmarkall")); - bind_key(WIDE(""), WIDE("startstop")); - bind_key(WIDE("e"), WIDE("edit-desc")); - bind_key(WIDE("\\"), WIDE("edit-time")); - bind_key(WIDE(""), WIDE("showhide-inv")); - bind_key(WIDE("c"), WIDE("copy")); - bind_key(WIDE("+"), WIDE("add-time")); - bind_key(WIDE("-"), WIDE("sub-time")); - bind_key(WIDE("/"), WIDE("search")); - bind_key(WIDE("$"), WIDE("sync")); - bind_key(WIDE(""), WIDE("prev")); - bind_key(WIDE(""), WIDE("next")); - bind_key(WIDE(":"), WIDE("execute")); - bind_key(WIDE("M"), WIDE("merge")); - bind_key(WIDE("r"), WIDE("interrupt")); - bind_key(WIDE("R"), WIDE("interrupt")); + bind_key(WIDE("?"), WIDE("help"), 0); + bind_key(WIDE("a"), WIDE("add"), 0); + bind_key(WIDE("A"), WIDE("add-old"), 0); + bind_key(WIDE("d"), WIDE("delete"), 0); + bind_key(WIDE("u"), WIDE("undelete"), 0); + bind_key(WIDE("q"), WIDE("quit"), 0); + bind_key(WIDE(""), WIDE("quit"), 0); + bind_key(WIDE("i"), WIDE("invoice"), 0); + bind_key(WIDE("b"), WIDE("billable"), 0); + bind_key(WIDE("m"), WIDE("mark"), 0); + bind_key(WIDE("U"), WIDE("unmarkall"), 0); + bind_key(WIDE(""), WIDE("startstop"), 0); + bind_key(WIDE("e"), WIDE("edit-desc"), 0); + bind_key(WIDE("\\"), WIDE("edit-time"), 0); + bind_key(WIDE(""), WIDE("showhide-inv"), 0); + bind_key(WIDE("c"), WIDE("copy"), 0); + bind_key(WIDE("+"), WIDE("add-time"), 0); + bind_key(WIDE("-"), WIDE("sub-time"), 0); + bind_key(WIDE("/"), WIDE("search"), 0); + bind_key(WIDE("$"), WIDE("sync"), 0); + bind_key(WIDE(""), WIDE("prev"), 0); + bind_key(WIDE(""), WIDE("next"), 0); + bind_key(WIDE(":"), WIDE("execute"), 0); + bind_key(WIDE("M"), WIDE("merge"), 0); + bind_key(WIDE("r"), WIDE("interrupt"), 0); + bind_key(WIDE("R"), WIDE("interrupt"), 0); /* * Make sure we can save (even if it's an empty file or nothing has @@ -496,7 +498,7 @@ struct kevent evs[2], rev; } #endif - while (GETCH(&c) != ERR) { + while (input_char(&c) != ERR) { #ifdef KEY_RESIZE if (c == KEY_RESIZE) continue; @@ -507,8 +509,13 @@ struct kevent evs[2], rev; TTS_TAILQ_FOREACH(bi, &bindings, bi_entries) { if (bi->bi_code != c) continue; - bi->bi_func->fn_hdl(); - goto next;; + + if (!macro_text && bi->bi_macro) + input_macro(bi->bi_macro); + else if (bi->bi_func) + bi->bi_func->fn_hdl(); + + goto next; } drawstatus(WIDE("Unknown command.")); @@ -1022,7 +1029,7 @@ function_t *f; * directly, rather than being looked up in the key table. */ void -bind_key(keyname, funcname) +bind_key(keyname, funcname, is_macro) const WCHAR *keyname, *funcname; { tkey_t *key = NULL; @@ -1040,15 +1047,23 @@ INT code; } else code = *keyname; - if ((func = find_func(funcname)) == NULL) { - errbox(WIDE("Unknown function \"%"FMT_L"s\""), funcname); - return; + if (!is_macro) { + if ((func = find_func(funcname)) == NULL) { + errbox(WIDE("Unknown function \"%"FMT_L"s\""), funcname); + return; + } } /* Do we already have a binding for this key? */ TTS_TAILQ_FOREACH(binding, &bindings, bi_entries) { if (binding->bi_code == code) { - binding->bi_func = func; + if (is_macro) { + binding->bi_func = NULL; + binding->bi_macro = STRDUP(funcname); + } else { + free(binding->bi_macro); + binding->bi_func = func; + } return; } } @@ -1058,8 +1073,13 @@ INT code; return; binding->bi_key = key; - binding->bi_func = func; binding->bi_code = code; + + if (is_macro) + binding->bi_macro = STRDUP(funcname); + else + binding->bi_func = func; + TTS_TAILQ_INSERT_TAIL(&bindings, binding, bi_entries); } @@ -1191,8 +1211,10 @@ vcmderr(msg, ap) fprintf(stderr, "\"%s\", line %d: %s\n", curfile, lineno, t); - } else + } else { + input_macro(NULL); vdrawstatus(msg, ap); + } } /* @@ -1349,3 +1371,32 @@ int h, m, s = 0; sleeptime = 0; } #endif /* USE_DARWIN_POWER */ + +void +input_macro(s) + WCHAR *s; +{ + free(macro_text); + macro_text = macro_pos = NULL; + + if (!s) + return; + + macro_text = macro_pos = STRDUP(s); +} + +int +input_char(c) + WCHAR *c; +{ + if (macro_pos) { + if (*macro_pos) { + *c = *macro_pos++; + return 0; + } + free(macro_text); + macro_text = macro_pos = NULL; + } + + return GETCH(c); +} diff --git a/ttsrc.sample b/ttsrc.sample index 9e3f599..51a8f1d 100644 --- a/ttsrc.sample +++ b/ttsrc.sample @@ -33,7 +33,7 @@ #set bill_advance 0 -#### Bindings +#### Bindings and macros # # Use the 'bind' command to (re)define keybindings. Type '?' while TTS is # running for a full list of key bindings. @@ -45,6 +45,12 @@ bind j next bind k prev +# Macros work in a similar way to bindings, except the second argument is a +# string which will be executed as if it was typed. For example, the +# following macro would add a new entry called "test", and set its timer to +# 30 minutes. +#macro t "atest\n+30:00\n" + #### Styling # # You can style UI elements with the 'style' command. Its syntax is: diff --git a/ui.c b/ui.c index 88d0461..c9e44ec 100644 --- a/ui.c +++ b/ui.c @@ -136,7 +136,7 @@ INT c; WADDSTR(pwin, WIDE(" [y/N]? ")); wattroff(pwin, A_BOLD); - while (WGETCH(pwin, &c) == ERR + while (input_char(&c) == ERR #ifdef KEY_RESIZE || (c == KEY_RESIZE) #endif @@ -190,7 +190,7 @@ histent_t *histpos = NULL; wmove(pwin, 0, STRLEN(msg) + 1 + pos); wrefresh(pwin); - if (WGETCH(pwin, &c) == ERR) + if (input_char(&c) == ERR) continue; switch (c) { diff --git a/wide.h b/wide.h index 85df84d..61ab2c8 100644 --- a/wide.h +++ b/wide.h @@ -78,4 +78,7 @@ extern int tts_wgetch(WINDOW *, int *); #define WSIZEOF(s) (sizeof(s) / sizeof(WCHAR)) +int input_char(WCHAR *); +void input_macro(WCHAR *); + #endif /* !TTS_WIDE_H */