initial version - sob - simple output bar | |
git clone git://git.codemadness.org/sob | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit 99d263504572720db6a3a78f1d1b7e0b50060ca7 | |
Author: Hiltjo Posthuma <[email protected]> | |
Date: Wed, 1 Oct 2014 22:45:24 +0000 | |
initial version | |
Diffstat: | |
A .gitignore | 3 +++ | |
A LICENSE | 21 +++++++++++++++++++++ | |
A Makefile | 48 +++++++++++++++++++++++++++++… | |
A README | 25 +++++++++++++++++++++++++ | |
A TODO | 20 ++++++++++++++++++++ | |
A arg.h | 63 +++++++++++++++++++++++++++++… | |
A config.def.h | 43 ++++++++++++++++++++++++++++++ | |
A config.mk | 25 +++++++++++++++++++++++++ | |
A scripts/complete_word | 21 +++++++++++++++++++++ | |
A scripts/history | 18 ++++++++++++++++++ | |
A sob.1 | 33 +++++++++++++++++++++++++++++… | |
A sob.c | 593 +++++++++++++++++++++++++++++… | |
A strlcpy.c | 47 +++++++++++++++++++++++++++++… | |
A util.h | 5 +++++ | |
14 files changed, 965 insertions(+), 0 deletions(-) | |
--- | |
diff --git a/.gitignore b/.gitignore | |
@@ -0,0 +1,3 @@ | |
+sob | |
+*.o | |
+core | |
diff --git a/LICENSE b/LICENSE | |
@@ -0,0 +1,21 @@ | |
+MIT/X Consortium License | |
+ | |
+(c) 2014 Hiltjo Posthuma <[email protected]> | |
+ | |
+Permission is hereby granted, free of charge, to any person obtaining a | |
+copy of this software and associated documentation files (the "Software"), | |
+to deal in the Software without restriction, including without limitation | |
+the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
+and/or sell copies of the Software, and to permit persons to whom the | |
+Software is furnished to do so, subject to the following conditions: | |
+ | |
+The above copyright notice and this permission notice shall be included in | |
+all copies or substantial portions of the Software. | |
+ | |
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
+DEALINGS IN THE SOFTWARE. | |
diff --git a/Makefile b/Makefile | |
@@ -0,0 +1,48 @@ | |
+include config.mk | |
+ | |
+SRC = sob.c strlcpy.c | |
+HDR = arg.h config.def.h util.h | |
+OBJ = ${SRC:.c=.o} | |
+ | |
+all: options sob | |
+ | |
+options: | |
+ @echo sob build options: | |
+ @echo "CFLAGS = ${CFLAGS}" | |
+ @echo "LDFLAGS = ${LDFLAGS}" | |
+ @echo "CC = ${CC}" | |
+ | |
+.c.o: | |
+ @echo CC $< | |
+ @${CC} -c ${CFLAGS} $< | |
+ | |
+${OBJ}: config.mk config.h | |
+ | |
+config.h: | |
+ @echo creating $@ from config.def.h | |
+ @cp config.def.h $@ | |
+ | |
+sob: ${OBJ} | |
+ @echo CC -o $@ | |
+ @${CC} -o $@ ${OBJ} ${LDFLAGS} | |
+ | |
+clean: | |
+ @echo cleaning | |
+ @rm -f sob ${OBJ} | |
+ | |
+install: all | |
+ @echo installing executable file to ${DESTDIR}${PREFIX}/bin | |
+ @mkdir -p ${DESTDIR}${PREFIX}/bin | |
+ @cp -f sob ${DESTDIR}${PREFIX}/bin | |
+ @chmod 755 ${DESTDIR}${PREFIX}/bin/sob | |
+ @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 | |
+ @mkdir -p ${DESTDIR}${MANPREFIX}/man1 | |
+ @sed "s/VERSION/${VERSION}/g" < sob.1 > ${DESTDIR}${MANPREFIX}/man1/so… | |
+ | |
+uninstall: | |
+ @echo removing executable file from ${DESTDIR}${PREFIX}/bin | |
+ @rm -f ${DESTDIR}${PREFIX}/bin/sob | |
+ @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 | |
+ @rm -f ${DESTDIR}${MANPREFIX}/man1/sob.1 | |
+ | |
+.PHONY: all options clean dist install uninstall | |
diff --git a/README b/README | |
@@ -0,0 +1,25 @@ | |
+Simple output bar | |
+================= | |
+ | |
+ | |
+Dependencies | |
+------------ | |
+- Ncurses (at the moment, alternatives will be investigated). | |
+- libc | |
+ | |
+ | |
+Features | |
+-------- | |
+- Custom prompt. | |
+- Easy to write scripts to pipe input/output. | |
+- Custom action on SIGWINCH (window resize). | |
+- Familiar and customizable keybinds (in config.h). | |
+- Example scripts for: | |
+ - Word completion. | |
+ - History | |
+ - Yank line (xsel). | |
+ | |
+ | |
+License | |
+------- | |
+See LICENSE file. | |
diff --git a/TODO b/TODO | |
@@ -0,0 +1,20 @@ | |
+13:32 <@TLH> Evil_Bob: please support C-p for up in history C-n for down in hi… | |
+ C-e, C-u, C-k (kill to rest of line), C-y, C-w | |
+ 13:32 <@TLH> what else | |
+ 13:32 <@TLH> C-j | |
+ 13:32 <@TLH> :P | |
+ 13:33 <@TLH> C-m as an alias to C-j | |
+ | |
+- test draw on wrapped lines. | |
+ | |
+- line_yank doesn't work with xclip, but works with xsel... | |
+ | |
+- optimize: | |
+ reduce redraws (line_redraw and line_cursor_update). | |
+ | |
+- selections / marks? (delete/pipe etc on selection)? | |
+- cycle completions? (with tab for example). | |
+- keybind to go to word next and previous (ctrl+arrow left/right). | |
+- prompt callback? allow to update prompt? | |
+ | |
+- try libedit, else just use ncurses. | |
diff --git a/arg.h b/arg.h | |
@@ -0,0 +1,63 @@ | |
+/* | |
+ * Copy me if you can. | |
+ * by 20h | |
+ */ | |
+ | |
+#ifndef ARG_H__ | |
+#define ARG_H__ | |
+ | |
+extern char *argv0; | |
+ | |
+/* use main(int argc, char *argv[]) */ | |
+#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ | |
+ argv[0] && argv[0][1]\ | |
+ && argv[0][0] == '-';\ | |
+ argc--, argv++) {\ | |
+ char argc_;\ | |
+ char **argv_;\ | |
+ int brk_;\ | |
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\ | |
+ argv++;\ | |
+ argc--;\ | |
+ break;\ | |
+ }\ | |
+ for (brk_ = 0, argv[0]++, argv_ = argv;\ | |
+ argv[0][0] && !brk_;\ | |
+ argv[0]++) {\ | |
+ if (argv_ != argv)\ | |
+ break;\ | |
+ argc_ = argv[0][0];\ | |
+ switch (argc_) | |
+ | |
+/* Handles obsolete -NUM syntax */ | |
+#define ARGNUM case '0':\ | |
+ case '1':\ | |
+ case '2':\ | |
+ case '3':\ | |
+ case '4':\ | |
+ case '5':\ | |
+ case '6':\ | |
+ case '7':\ | |
+ case '8':\ | |
+ case '9' | |
+ | |
+#define ARGEND }\ | |
+ } | |
+ | |
+#define ARGC() argc_ | |
+ | |
+#define ARGNUMF(base) (brk_ = 1, estrtol(argv[0], (base))) | |
+ | |
+#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ | |
+ ((x), abort(), (char *)0) :\ | |
+ (brk_ = 1, (argv[0][1] != '\0')?\ | |
+ (&argv[0][1]) :\ | |
+ (argc--, argv++, argv[0]))) | |
+ | |
+#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ | |
+ (char *)0 :\ | |
+ (brk_ = 1, (argv[0][1] != '\0')?\ | |
+ (&argv[0][1]) :\ | |
+ (argc--, argv++, argv[0]))) | |
+ | |
+#endif | |
diff --git a/config.def.h b/config.def.h | |
@@ -0,0 +1,43 @@ | |
+static const char *prompt = "> "; | |
+static char id[256] = ""; | |
+static const char *completewordcmd[] = { "./scripts/complete_word", id, NULL }; | |
+static const char *historycmd[] = { "./scripts/history", id, NULL }; | |
+static const char *yankcmd[] = { "/bin/sh", "-c", "/bin/xsel -i -p", N… | |
+static const char *resizecmd = "tmux resize-pane -y 4 2> /dev/null"; | |
+ | |
+static struct keybind { | |
+ int key; | |
+ void (*func)(void); | |
+} keybinds[] = { | |
+ { CONTROL('T'), line_delwordcursor }, | |
+ { CONTROL('A'), line_cursor_begin }, | |
+ { CONTROL('E'), line_cursor_end }, | |
+ { KEY_HOME, line_cursor_begin }, | |
+ { KEY_END, line_cursor_end }, | |
+ { CONTROL('B'), line_cursor_prev }, | |
+ { KEY_LEFT, line_cursor_prev }, | |
+ { CONTROL('F'), line_cursor_next }, | |
+ { KEY_RIGHT, line_cursor_next }, | |
+ { CONTROL('W'), line_delwordback }, | |
+ { CONTROL('H'), line_delcharback }, | |
+ { CONTROL('U'), line_clear }, | |
+ { KEY_DL, line_clear }, | |
+ { CONTROL('K'), line_deltoend }, | |
+ { KEY_SDC, line_delcharnext }, | |
+ { KEY_DC, line_delcharnext }, | |
+ { KEY_BACKSPACE, line_delcharback }, | |
+ { CONTROL('M'), line_newline }, | |
+ { CONTROL('J'), line_newline }, | |
+ { '\r', line_newline }, | |
+ { '\n', line_newline }, | |
+ { KEY_ENTER, line_newline }, | |
+ { CONTROL('Y'), line_yank }, | |
+ { KEY_EXIT, line_exit }, | |
+ { 0x04, line_exit }, /* EOT */ | |
+ { KEY_EOL, line_deltoend }, | |
+ { KEY_UP, history_menu }, | |
+ { KEY_DOWN, history_menu }, | |
+ { CONTROL('P'), history_menu }, | |
+ { CONTROL('N'), history_menu }, | |
+ { '\t', complete_word }, | |
+}; | |
diff --git a/config.mk b/config.mk | |
@@ -0,0 +1,25 @@ | |
+VERSION = 0.1 | |
+ | |
+# Customize below to fit your system | |
+ | |
+# paths | |
+PREFIX = /usr/local | |
+MANPREFIX = ${PREFIX}/share/man | |
+ | |
+# includes and libs | |
+INCS = -I. -I/usr/include | |
+LIBS = -L/usr/lib -lc -lncurses | |
+ | |
+# flags | |
+CPPFLAGS = -DVERSION=\"${VERSION}\" -D_POSIX_C_SOURCE=200809 -D_BSD_SOURCE | |
+ | |
+# debug | |
+#CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} | |
+#LDFLAGS = ${LIBS} | |
+ | |
+# release | |
+CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} | |
+LDFLAGS = -s ${LIBS} | |
+ | |
+# compiler and linker | |
+CC = cc | |
diff --git a/scripts/complete_word b/scripts/complete_word | |
@@ -0,0 +1,21 @@ | |
+#!/bin/sh | |
+ | |
+grepword() { | |
+ grep -E "^$1" < out.log | |
+} | |
+ | |
+id="" | |
+test x"$1" != x"" && id="$1" | |
+read -r word | |
+ | |
+#if test x"$DISPLAY" = x""; then | |
+# line=$(grepword "$word" | sort | uniq | slmenu -l 20) | |
+#else | |
+# line=$(grepword "$word" | sort | uniq | dmenu -l 20) | |
+#fi | |
+ | |
+line=$(grepword "$word" | sort | uniq | dmenu -l 20) | |
+ | |
+if test x"$line" != x""; then | |
+ printf '%s\n' "$line" | |
+fi | |
diff --git a/scripts/history b/scripts/history | |
@@ -0,0 +1,18 @@ | |
+#!/bin/sh | |
+id="" | |
+test x"$1" != x"" && id="$1" | |
+file="out.log" | |
+ | |
+test -f "$file" || exit 1 | |
+ | |
+#if test x"$DISPLAY" = x""; then | |
+# line=$(tail -n 100 "$file" | slmenu -l 20) | |
+#else | |
+# line=$(tail -n 100 "$file" | dmenu -l 20) | |
+#fi | |
+ | |
+line=$(tail -n 100 "$file" | dmenu -l 20) | |
+ | |
+if test x"$line" != x""; then | |
+ printf '%s\n' "$line" | |
+fi | |
diff --git a/sob.1 b/sob.1 | |
@@ -0,0 +1,33 @@ | |
+.TH SOB 1 sob\-VERSION | |
+.SH NAME | |
+sob \- simple output bar | |
+.SH SYNOPSIS | |
+.B sob | |
+.RB < \-f | |
+.IR outfile > | |
+.RB [ \-l | |
+.IR line ] | |
+.RB [ \-p | |
+.IR prompt ] | |
+.RB [ \-r | |
+.IR resizecmd ] | |
+.SH DESCRIPTION | |
+sob is a simple line editor. | |
+.SH OPTIONS | |
+.TP | |
+.B \-f " outfile" | |
+output file. | |
+.TP | |
+.B \-i " id" | |
+extra parameter passed to program. | |
+.TP | |
+.B \-l " line" | |
+initial input on line. | |
+.TP | |
+.B \-p " prompt" | |
+prompt string. | |
+.TP | |
+.B \-r " resizecmd" | |
+command to run on SIGWINCH (ncurses KEY_RESIZE). | |
+.SH BUGS | |
+Please report them! | |
diff --git a/sob.c b/sob.c | |
@@ -0,0 +1,593 @@ | |
+#include <ctype.h> | |
+#include <errno.h> | |
+#include <fcntl.h> | |
+#include <limits.h> | |
+#include <locale.h> | |
+#include <signal.h> | |
+#include <stdio.h> | |
+#include <stdlib.h> | |
+#include <string.h> | |
+#include <sys/select.h> | |
+#include <unistd.h> | |
+ | |
+#include <ncurses.h> | |
+ | |
+#include "arg.h" | |
+char *argv0; | |
+ | |
+#include "util.h" | |
+ | |
+#define CONTROL(ch) ((ch)^0x40) | |
+#define LEN(x) (sizeof (x) / sizeof *(x)) | |
+#define MAX(A, B) ((A) > (B) ? (A) : (B)) | |
+ | |
+struct line { | |
+ char line[BUFSIZ]; | |
+ size_t len; | |
+ size_t pos; | |
+}; | |
+ | |
+static struct line line; | |
+static int isrunning = 1; | |
+static char * outname = NULL; | |
+static WINDOW * win = NULL; | |
+ | |
+static void line_clear(void); | |
+static void line_copywordcursor(char *buf, size_t bufsiz); | |
+static void line_cursor_begin(void); | |
+static void line_cursor_end(void); | |
+static void line_cursor_next(void); | |
+static void line_cursor_prev(void); | |
+static void line_cursor_update(void); | |
+static void line_delcharback(void); | |
+static void line_delcharnext(void); | |
+static void line_deltoend(void); | |
+static void line_delwordback(void); | |
+static void line_delwordcursor(void); | |
+static void line_exit(void); | |
+static void line_getwordpos(unsigned int *start, unsigned int *end); | |
+static void line_insertchar(int c); | |
+static void line_inserttext(const char *s); | |
+static void line_newline(void); | |
+static int line_out(void); | |
+static void line_prompt(void); | |
+static int line_pipeto(char **cmd); | |
+static void line_redraw(size_t max); | |
+static void line_set(const char *s); | |
+static void line_yank(void); | |
+static void history_menu(void); | |
+static void complete_word(void); | |
+static int pipereadline(int fd_in, int fd_out, char *writestr, char *outbuf, | |
+ size_t outbufsiz); | |
+static int pipecmd(char *cmd[], char *writestr, char *outbuf, | |
+ size_t outbufsiz); | |
+static void sighandler(int signum); | |
+static void setup(void); | |
+static void cleanup(void); | |
+static void run(void); | |
+static void usage(void); | |
+ | |
+#include "config.h" | |
+ | |
+static void | |
+line_insertchar(int c) | |
+{ | |
+ char s[2]; | |
+ | |
+ s[0] = c; | |
+ s[1] = '\0'; | |
+ line_inserttext(s); | |
+} | |
+ | |
+static void | |
+line_inserttext(const char *s) | |
+{ | |
+ size_t len; | |
+ | |
+ len = strlen(s); | |
+ if(line.pos + len + 1 > sizeof(line.line)) | |
+ return; | |
+ /* append */ | |
+ if(line.pos == line.len) { | |
+ memmove(&line.line[line.pos], s, len); | |
+ } else { | |
+ /* insert */ | |
+ memmove(&line.line[line.pos + len], &line.line[line.pos], line… | |
+ memcpy(&line.line[line.pos], s, len); | |
+ } | |
+ line.pos += len; | |
+ line.len += len; | |
+ line.line[line.len + 1] = '\0'; | |
+} | |
+ | |
+static void | |
+line_set(const char *s) { | |
+ strlcpy(line.line, s, sizeof(line.line)); | |
+ line.len = strlen(line.line); | |
+ line.pos = line.len; | |
+} | |
+ | |
+static void | |
+line_prompt(void) | |
+{ | |
+ size_t i; | |
+ | |
+ wmove(win, 0, 0); | |
+ for(i = 0; prompt[i]; i++) | |
+ waddch(win, prompt[i]); | |
+} | |
+ | |
+static void | |
+line_redraw(size_t max) | |
+{ | |
+ size_t n; | |
+ | |
+ line_prompt(); | |
+ | |
+ for(n = 0; line.line[n] && n < line.len && n < max; n++) | |
+ waddch(win, line.line[n]); | |
+ for(; n < max; n++) | |
+ waddch(win, ' '); | |
+ wrefresh(win); | |
+} | |
+ | |
+static int | |
+line_out(void) | |
+{ | |
+ FILE *fp; | |
+ if(!(fp = fopen(outname, "a"))) { | |
+ fprintf(stderr, "fopen: '%s': %s\n", outname, strerror(errno)); | |
+ return -1; | |
+ } | |
+ fprintf(fp, "%s\n", line.line); | |
+ fflush(fp); | |
+ fclose(fp); | |
+ return 0; | |
+} | |
+ | |
+static void | |
+line_cursor_update(void) | |
+{ | |
+ wmove(win, 0, line.pos + strlen(prompt)); | |
+} | |
+ | |
+static void | |
+line_cursor_begin(void) | |
+{ | |
+ line.pos = 0; | |
+ line_cursor_update(); | |
+} | |
+ | |
+static void | |
+line_cursor_prev(void) | |
+{ | |
+ if(line.pos > 0) | |
+ line.pos--; | |
+ line_cursor_update(); | |
+} | |
+ | |
+static void | |
+line_cursor_next(void) | |
+{ | |
+ if(line.pos < line.len) | |
+ line.pos++; | |
+ line_cursor_update(); | |
+} | |
+ | |
+static void | |
+line_cursor_end(void) | |
+{ | |
+ line.pos = line.len; | |
+ line_cursor_update(); | |
+} | |
+ | |
+static void | |
+line_clear(void) | |
+{ | |
+ line.line[0] = '\0'; | |
+ line_redraw(line.len); | |
+ line.len = 0; | |
+ line_cursor_begin(); | |
+} | |
+ | |
+static void | |
+line_delcharnext(void) | |
+{ | |
+ size_t oldlen = line.len; | |
+ | |
+ if(line.pos == line.len || line.len <= 0) | |
+ return; | |
+ | |
+ memmove(&line.line[line.pos], &line.line[line.pos + 1], | |
+ line.line[line.len - line.pos - 1]); | |
+ line.len--; | |
+ line.line[line.len] = '\0'; | |
+ line_redraw(oldlen); | |
+ line_cursor_update(); | |
+} | |
+ | |
+static void | |
+line_delcharback(void) | |
+{ | |
+ size_t oldlen = line.len; | |
+ | |
+ if(line.pos <= 0 || line.len <= 0) | |
+ return; | |
+ memmove(&line.line[line.pos - 1], &line.line[line.pos], | |
+ line.line[line.len - line.pos]); | |
+ line.len--; | |
+ line.line[line.len] = '\0'; | |
+ line_redraw(oldlen); | |
+ line_cursor_prev(); | |
+} | |
+ | |
+static void | |
+line_deltoend(void) | |
+{ | |
+ size_t oldlen = line.len; | |
+ | |
+ line.line[line.pos] = '\0'; | |
+ line.len = line.pos; | |
+ line_redraw(oldlen); | |
+ line_cursor_end(); | |
+} | |
+ | |
+static void | |
+line_delwordcursor(void) | |
+{ | |
+ unsigned int s, e; | |
+ size_t len, oldlen = line.len; | |
+ | |
+ line_getwordpos(&s, &e); | |
+ | |
+ memmove(&line.line[s], &line.line[e], line.len - e); | |
+ len = e - s; | |
+ line.len -= len; | |
+ line.pos = s; | |
+ line.line[line.len] = '\0'; | |
+ line_redraw(MAX(line.len, oldlen)); | |
+ line_cursor_update(); | |
+} | |
+ | |
+static void | |
+line_delwordback(void) | |
+{ | |
+ size_t i, len, oldlen = line.len; | |
+ | |
+ if(line.pos <= 0 || line.len <= 0) | |
+ return; | |
+ | |
+ i = line.pos; | |
+ while(i > 0 && isspace(line.line[i - 1])) | |
+ i--; | |
+ while(i > 0 && !isspace(line.line[i - 1])) | |
+ i--; | |
+ | |
+ len = line.len - line.pos; | |
+ if(len > 0) | |
+ memmove(&line.line[i], &line.line[line.pos], | |
+ line.len - line.pos); | |
+ len = line.pos - i; | |
+ line.pos = i; | |
+ line.len -= len; | |
+ line.line[line.len] = '\0'; | |
+ line_redraw(oldlen); | |
+ line_cursor_update(); | |
+} | |
+ | |
+static void | |
+line_newline(void) | |
+{ | |
+ line_out(); | |
+ line_clear(); | |
+ line_prompt(); | |
+ wrefresh(win); | |
+} | |
+ | |
+static void | |
+line_exit(void) | |
+{ | |
+ line_newline(); | |
+ isrunning = 0; | |
+} | |
+ | |
+static void | |
+line_getwordpos(unsigned int *start, unsigned int *end) | |
+{ | |
+ size_t i; | |
+ | |
+ i = line.pos; | |
+ while(i > 0 && !isspace(line.line[i - 1])) | |
+ i--; | |
+ if(start) | |
+ *start = i; | |
+ i = line.pos; | |
+ while(line.line[i] && i < line.len && !isspace(line.line[i])) | |
+ i++; | |
+ if(end) | |
+ *end = i; | |
+} | |
+ | |
+static void | |
+line_copywordcursor(char *buf, size_t bufsiz) | |
+{ | |
+ unsigned int s, e; | |
+ size_t len; | |
+ | |
+ line_getwordpos(&s, &e); | |
+ len = e - s; | |
+ /* truncate */ | |
+ if(len + 1 > bufsiz) | |
+ len = bufsiz - 1; | |
+ memcpy(buf, &line.line[s], len); | |
+ buf[len + 1] = '\0'; | |
+} | |
+ | |
+static void | |
+complete_word(void) | |
+{ | |
+ char wordbuf[BUFSIZ], outbuf[BUFSIZ]; | |
+ size_t oldlen = line.len; | |
+ | |
+ outbuf[0] = '\0'; | |
+ line_copywordcursor(wordbuf, sizeof(wordbuf)); | |
+ | |
+ if(pipecmd((char**)completewordcmd, wordbuf, outbuf, | |
+ sizeof(outbuf)) == -1) | |
+ return; | |
+ if(outbuf[0] == '\0') | |
+ return; | |
+ | |
+ line_delwordcursor(); | |
+ line_inserttext(outbuf); | |
+ line_redraw(MAX(line.len, oldlen)); | |
+ line_cursor_update(); | |
+} | |
+ | |
+static void | |
+line_yank(void) | |
+{ | |
+ line_pipeto((char**)yankcmd); | |
+} | |
+ | |
+static void | |
+history_menu(void) | |
+{ | |
+ line_pipeto((char**)historycmd); | |
+} | |
+ | |
+static int | |
+pipereadline(int fd_in, int fd_out, char *writestr, char *outbuf, | |
+ size_t outbufsiz) | |
+{ | |
+ char buf[PIPE_BUF], *p; | |
+ struct timeval tv; | |
+ fd_set fdr, fdw; | |
+ int r, w, maxfd, status = -1, haswritten = 0; | |
+ | |
+ for(;;) { | |
+ FD_ZERO(&fdr); | |
+ FD_ZERO(&fdw); | |
+ if(haswritten) { | |
+ FD_SET(fd_in, &fdr); | |
+ maxfd = fd_in; | |
+ } else { | |
+ FD_SET(fd_out, &fdw); | |
+ maxfd = fd_out; | |
+ } | |
+ memset(&tv, 0, sizeof(tv)); | |
+ tv.tv_sec = 0; | |
+ tv.tv_usec = 200; | |
+ | |
+ if((r = select(maxfd + 1, haswritten ? &fdr : NULL, | |
+ haswritten ? NULL : &fdw, NULL, &tv)) == -1) | |
+ goto fini; | |
+ else if(!r) /* timeout */ | |
+ continue; | |
+ | |
+ if(haswritten) { | |
+ if(FD_ISSET(fd_in, &fdr)) { | |
+ /* read until newline */ | |
+ if((r = read(fd_in, buf, sizeof(buf))) == -1) | |
+ goto fini; | |
+ buf[r] = '\0'; | |
+ if((p = strpbrk(buf, "\r\n"))) | |
+ *p = '\0'; | |
+ strlcpy(outbuf, buf, sizeof(outbuf)); | |
+ status = 0; | |
+ goto fini; | |
+ } | |
+ } else { | |
+ if(FD_ISSET(fd_out, &fdw)) { | |
+ /* write error */ | |
+ if((w = write(fd_out, writestr, strlen(writest… | |
+ goto fini; | |
+ close(fd_out); /* sends EOF */ | |
+ haswritten = 1; | |
+ } | |
+ } | |
+ } | |
+fini: | |
+ close(fd_in); | |
+ close(fd_out); | |
+ return status; | |
+} | |
+ | |
+static int | |
+pipecmd(char *cmd[], char *writestr, char *outbuf, size_t outbufsiz) | |
+{ | |
+ struct sigaction sa; | |
+ pid_t pid; | |
+ int pc[2], cp[2]; | |
+ | |
+ if ((pipe(pc) == -1) || (pipe(cp) == -1)) { | |
+ perror("pipe"); | |
+ return -1; | |
+ } | |
+ pid = fork(); | |
+ if (pid == -1) { | |
+ perror("fork"); | |
+ return -1; | |
+ } else if (pid == 0) { | |
+ /* child */ | |
+ close(cp[0]); | |
+ close(pc[1]); | |
+ if (dup2(pc[0], STDIN_FILENO) == -1 || | |
+ dup2(cp[1], STDOUT_FILENO) == -1) { | |
+ perror("dup2"); | |
+ return -1; | |
+ } | |
+ if(execv(cmd[0], (char**)cmd) == -1) { | |
+ perror("execv"); | |
+ _exit(EXIT_FAILURE); /* NOTE: must be _exit */ | |
+ } | |
+ _exit(EXIT_SUCCESS); | |
+ } else { | |
+ /* parent */ | |
+ close(pc[0]); | |
+ close(cp[1]); | |
+ | |
+ /* ignore SIGPIPE, we handle this for write(). */ | |
+ memset(&sa, 0, sizeof(sa)); | |
+ sa.sa_flags = SA_RESTART; | |
+ sa.sa_handler = SIG_IGN; | |
+ sigaction(SIGPIPE, &sa, NULL); | |
+ | |
+ if(pipereadline(cp[0], pc[1], writestr, outbuf, outbufsiz) == … | |
+ return -1; | |
+ } | |
+ return 0; | |
+} | |
+ | |
+static int | |
+line_pipeto(char **cmd) | |
+{ | |
+ size_t oldlen = line.len; | |
+ | |
+ if(pipecmd(cmd, line.line, line.line, sizeof(line.line)) == -1) | |
+ return -1; | |
+ line.len = strlen(line.line); | |
+ line_redraw(MAX(line.len, oldlen)); | |
+ line_cursor_end(); | |
+ return 0; | |
+} | |
+ | |
+static void | |
+sighandler(int signum) | |
+{ | |
+ if(signum == SIGTERM) { | |
+ isrunning = 0; | |
+ cleanup(); | |
+ } | |
+} | |
+ | |
+static void | |
+setup(void) | |
+{ | |
+ struct sigaction sa; | |
+ | |
+ initscr(); | |
+ win = stdscr; | |
+ cbreak(); | |
+ noecho(); | |
+ nonl(); | |
+ nodelay(win, FALSE); | |
+ keypad(win, TRUE); | |
+ curs_set(1); | |
+ ESCDELAY = 20; | |
+ | |
+ /* signal handling */ | |
+ memset(&sa, 0, sizeof(sa)); | |
+ sa.sa_flags = SA_RESTART; | |
+ sa.sa_handler = sighandler; | |
+ sigaction(SIGTERM, &sa, NULL); | |
+} | |
+ | |
+static void | |
+cleanup(void) | |
+{ | |
+ endwin(); | |
+} | |
+ | |
+static void | |
+run(void) | |
+{ | |
+ size_t i; | |
+ int c, ismatch = 0; | |
+ | |
+ line_redraw(line.len); | |
+ while(isrunning) { | |
+ c = wgetch(win); | |
+ switch(c) { | |
+ case ERR: | |
+ isrunning = 0; | |
+ break; | |
+ case 0x1b: /* ignore unbinded escape sequences */ | |
+ nodelay(win, TRUE); | |
+ while((c = wgetch(win)) != ERR); | |
+ nodelay(win, FALSE); | |
+ break; | |
+ case KEY_RESIZE: | |
+ if(resizecmd && resizecmd[0]) | |
+ system(resizecmd); | |
+ break; | |
+ default: | |
+ ismatch = 0; | |
+ for(i = 0; i < LEN(keybinds); i++) { | |
+ if(keybinds[i].key == c) { | |
+ ismatch = 1; | |
+ keybinds[i].func(); | |
+ break; | |
+ } | |
+ } | |
+ if(!ismatch) { | |
+ line_insertchar(c); | |
+ line_redraw(line.len); | |
+ line_cursor_update(); | |
+ wrefresh(win); | |
+ } | |
+ } | |
+ } | |
+} | |
+ | |
+static void | |
+usage(void) | |
+{ | |
+ fprintf(stderr, "usage: %s <-f outfile> [-i id] [-l line] [-p prompt] " | |
+ "[-r resizecmd]\n", argv0); | |
+ exit(EXIT_FAILURE); | |
+} | |
+ | |
+int | |
+main(int argc, char **argv) | |
+{ | |
+ ARGBEGIN { | |
+ case 'f': | |
+ outname = EARGF(usage()); | |
+ break; | |
+ case 'i': | |
+ strlcpy(id, EARGF(usage()), sizeof(id)); | |
+ break; | |
+ case 'l': | |
+ line_set(EARGF(usage())); | |
+ break; | |
+ case 'p': | |
+ prompt = EARGF(usage()); | |
+ break; | |
+ case 'r': | |
+ resizecmd = EARGF(usage()); | |
+ break; | |
+ default: | |
+ usage(); | |
+ } ARGEND; | |
+ | |
+ if(!outname) | |
+ usage(); | |
+ | |
+ setlocale(LC_ALL, ""); | |
+ setup(); | |
+ run(); | |
+ cleanup(); | |
+ | |
+ return EXIT_SUCCESS; | |
+} | |
diff --git a/strlcpy.c b/strlcpy.c | |
@@ -0,0 +1,47 @@ | |
+/* | |
+ * Copyright (c) 1998 Todd C. Miller <[email protected]> | |
+ * | |
+ * Permission to use, copy, modify, and distribute this software for any | |
+ * purpose with or without fee is hereby granted, provided that the above | |
+ * copyright notice and this permission notice appear in all copies. | |
+ * | |
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
+ */ | |
+ | |
+#include <sys/types.h> | |
+#include <string.h> | |
+#include "util.h" | |
+ | |
+/* | |
+ * Copy src to string dst of size siz. At most siz-1 characters | |
+ * will be copied. Always NUL terminates (unless siz == 0). | |
+ * Returns strlen(src); if retval >= siz, truncation occurred. | |
+ */ | |
+size_t | |
+strlcpy(char *dst, const char *src, size_t siz) | |
+{ | |
+ char *d = dst; | |
+ const char *s = src; | |
+ size_t n = siz; | |
+ /* Copy as many bytes as will fit */ | |
+ if (n != 0) { | |
+ while (--n != 0) { | |
+ if ((*d++ = *s++) == '\0') | |
+ break; | |
+ } | |
+ } | |
+ /* Not enough room in dst, add NUL and traverse rest of src */ | |
+ if (n == 0) { | |
+ if (siz != 0) | |
+ *d = '\0'; /* NUL-terminate dst */ | |
+ while (*s++) | |
+ ; | |
+ } | |
+ return(s - src - 1); /* count does not include NUL */ | |
+} | |
diff --git a/util.h b/util.h | |
@@ -0,0 +1,5 @@ | |
+#include <stdio.h> | |
+#include <unistd.h> | |
+ | |
+#undef strlcpy | |
+size_t strlcpy(char *dst, const char *src, size_t siz); |