Exemple #1
0
/*
 *	Input an escaped token.  Possible escape chars are single-quote,
 *	double-quote and backslash.  Only the first is a valid escape for
 *	rc; the others are just inserted into the receiving buffer.
 */
int
escapetoken(Biobuf *bp, Bufblock *buf, int preserve, int esc)
{
	int c, line;

	if(esc != '\'')
		return 1;

	line = mkinline;
	while((c = nextrune(bp, 0)) > 0){
		if(c == '\''){
			if(preserve)
				rinsert(buf, c);
			c = Bgetrune(bp);
			if (c < 0)
				break;
			if(c != '\''){
				Bungetrune(bp);
				return 1;
			}
		}
		rinsert(buf, c);
	}
	SYNERR(line); fprint(2, "missing closing %c\n", esc);
	return 0;
}
Exemple #2
0
void copy_first() {
	if (valid) {
		strcpy(text, valid->text);
		while (text[cursor] != '\0' && cursor < MAX_LEN - 1)
			cursor = nextrune(+1);					
		update_valid_options();
	}
}
Exemple #3
0
static int lex(struct cstate *g)
{
	int quoted = nextrune(g);
	if (quoted) {
		switch (g->yychar) {
		case 'b': return L_WORD;
		case 'B': return L_NWORD;
		case 'd': newcclass(g); addranges_d(g); return L_CCLASS;
		case 's': newcclass(g); addranges_s(g); return L_CCLASS;
		case 'w': newcclass(g); addranges_w(g); return L_CCLASS;
		case 'D': newcclass(g); addranges_d(g); return L_NCCLASS;
		case 'S': newcclass(g); addranges_s(g); return L_NCCLASS;
		case 'W': newcclass(g); addranges_w(g); return L_NCCLASS;
		case '0': g->yychar = 0; return L_CHAR;
		}
		if (g->yychar >= '0' && g->yychar <= '9') {
			g->yychar -= '0';
			if (*g->source >= '0' && *g->source <= '9')
				g->yychar = g->yychar * 10 + *g->source++ - '0';
			return L_REF;
		}
		return L_CHAR;
	}

	switch (g->yychar) {
	case 0:
	case '$': case ')': case '*': case '+':
	case '.': case '?': case '^': case '|':
		return g->yychar;
	}

	if (g->yychar == '{')
		return lexcount(g);
	if (g->yychar == '[')
		return lexclass(g);
	if (g->yychar == '(') {
		if (g->source[0] == '?') {
			if (g->source[1] == ':') {
				g->source += 2;
				return L_NC;
			}
			if (g->source[1] == '=') {
				g->source += 2;
				return L_PLA;
			}
			if (g->source[1] == '!') {
				g->source += 2;
				return L_NLA;
			}
		}
		return '(';
	}

	return L_CHAR;
}
Exemple #4
0
/*
 *	Assemble a line skipping blank lines, comments, and eliding
 *	escaped newlines
 */
int
assline(Biobuf *bp, Bufblock *buf)
{
	int c;
	int lastc;

	buf->current=buf->start;
	while ((c = nextrune(bp, 1)) >= 0){
		switch(c)
		{
		case '\r':		/* consumes CRs for Win95 */
			continue;
		case '\n':
			if (buf->current != buf->start) {
				insert(buf, 0);
				return 1;
			}
			break;		/* skip empty lines */
		case '\\':
		case '\'':
		case '"':
			rinsert(buf, c);
			if (escapetoken(bp, buf, 1, c) == 0)
				Exit();
			break;
		case '`':
			if (bquote(bp, buf) == 0)
				Exit();
			break;
		case '#':
			lastc = '#';
			while ((c = Bgetc(bp)) != '\n') {
				if (c < 0)
					goto eof;
				if(c != '\r')
					lastc = c;
			}
			mkinline++;
			if (lastc == '\\')
				break;		/* propagate escaped newlines??*/
			if (buf->current != buf->start) {
				insert(buf, 0);
				return 1;
			}
			break;
		default:
			rinsert(buf, c);
			break;
		}
	}
eof:
	insert(buf, 0);
	return *buf->start != 0;
}
Exemple #5
0
/*
 *	assemble a back-quoted shell command into a buffer
 */
static int
bquote(Biobuf *bp, Bufblock *buf)
{
	int c, line, term, depth;
	int start;

	line = mkinline;
	while((c = Bgetrune(bp)) == ' ' || c == '\t')
			;
	if(c == '{'){
		term = '}';		/* rc style */
		while((c = Bgetrune(bp)) == ' ' || c == '\t')
			;
	} else
		term = '`';		/* sh style */

	depth = 1;
	start = buf->current-buf->start;
	for(;c > 0; c = nextrune(bp, 0)){
		if(c == '{' && term == '}')
			depth++;
		if(c == term && --depth == 0){
			insert(buf, '\n');
			insert(buf,0);
			buf->current = buf->start+start;
			execinit();
			execsh(0, buf->current, buf, envy);
			return 1;
		}
		if(c == '\n')
			break;
		if(c == '\'' || c == '"' || c == '\\'){
			insert(buf, c);
			if(!escapetoken(bp, buf, 1, c))
				return 0;
			continue;
		}
		rinsert(buf, c);
	}
	SYNERR(line);
	fprint(2, "missing closing %c after `\n", term);
	return 0;
}
Exemple #6
0
/*
 *	Input an escaped token.  Possible escape chars are single-quote,
 *	double-quote and backslash.
 */
static int
shescapetoken(Biobuf *bp, Bufblock *buf, int preserve, int esc)
{
	int c, line;

	if(esc == '\\') {
		c = Bgetrune(bp);
		if(c == '\r')
			c = Bgetrune(bp);
		if (c == '\n')
			mkinline++;
		rinsert(buf, c);
		return 1;
	}

	line = mkinline;
	while((c = nextrune(bp, 0)) >= 0){
		if(c == esc){
			if(preserve)
				rinsert(buf, c);
			return 1;
		}
		if(c == '\\') {
			rinsert(buf, c);
			c = Bgetrune(bp);
			if(c == '\r')
				c = Bgetrune(bp);
			if (c < 0)
				break;
			if (c == '\n')
				mkinline++;
		}
		rinsert(buf, c);
	}
	SYNERR(line); fprint(2, "missing closing %c\n", esc);
	return 0;
}
Exemple #7
0
void
keypress(XKeyEvent *ev) {
	char buf[32];
	int len;
	KeySym ksym = NoSymbol;
	Status status;

	len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status);
	if(status == XBufferOverflow)
		return;
	if(ev->state & ControlMask)
		switch(ksym) {
		case XK_a: ksym = XK_Home;      break;
		case XK_b: ksym = XK_Left;      break;
		case XK_c: ksym = XK_Escape;    break;
		case XK_d: ksym = XK_Delete;    break;
		case XK_e: ksym = XK_End;       break;
		case XK_f: ksym = XK_Right;     break;
		case XK_g: ksym = XK_Escape;    break;
		case XK_h: ksym = XK_BackSpace; break;
		case XK_i: ksym = XK_Tab;       break;
		case XK_j: /* fallthrough */
		case XK_J: ksym = XK_Return;    break;
		case XK_m: /* fallthrough */
		case XK_M: ksym = XK_Return;    break;
		case XK_n: ksym = XK_Down;      break;
		case XK_p: ksym = XK_Up;        break;

		case XK_k: /* delete right */
			text[cursor] = '\0';
			match();
			break;
		case XK_u: /* delete left */
			insert(NULL, 0 - cursor);
			break;
		case XK_w: /* delete word */
			while(cursor > 0 && text[nextrune(-1)] == ' ')
				insert(NULL, nextrune(-1) - cursor);
			while(cursor > 0 && text[nextrune(-1)] != ' ' && text[nextrune(-1)] != '/')
				insert(NULL, nextrune(-1) - cursor);
			break;
		case XK_y: /* paste selection */
			XConvertSelection(dc->dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
			                  utf8, utf8, win, CurrentTime);
			return;
		default:
			return;
		}
	else if(ev->state & Mod1Mask)
		switch(ksym) {
		case XK_g: ksym = XK_Home;  break;
		case XK_G: ksym = XK_End;   break;
		case XK_h: ksym = XK_Up;    break;
		case XK_j: ksym = XK_Next;  break;
		case XK_k: ksym = XK_Prior; break;
		case XK_l: ksym = XK_Down;  break;
		default:
			return;
		}
	switch(ksym) {
	default:
		if(!iscntrl(*buf))
			insert(buf, len);
		break;
	case XK_Delete:
		if(text[cursor] == '\0')
			return;
		cursor = nextrune(+1);
		/* fallthrough */
	case XK_BackSpace:
		if(cursor == 0)
			return;
		insert(NULL, nextrune(-1) - cursor);
		break;
	case XK_End:
		if(text[cursor] != '\0') {
			cursor = strlen(text);
			break;
		}
		if(next) {
			/* jump to end of list and position items in reverse */
			curr = matchend;
			calcoffsets();
			curr = prev;
			calcoffsets();
			while(next && (curr = curr->right))
				calcoffsets();
		}
		sel = matchend;
		break;
	case XK_Escape:
        ret = EXIT_FAILURE;
        running = False;
	case XK_Home:
		if(sel == matches) {
			cursor = 0;
			break;
		}
		sel = curr = matches;
		calcoffsets();
		break;
	case XK_Left:
		if(cursor > 0 && (!sel || !sel->left || lines > 0)) {
			cursor = nextrune(-1);
            break;
		}
		if(lines > 0)
			return;
		/* fallthrough */
	case XK_Up:
		if(sel && sel->left && (sel = sel->left)->right == curr) {
			curr = prev;
			calcoffsets();
		}
		break;
	case XK_Next:
		if(!next)
			return;
		sel = curr = next;
		calcoffsets();
		break;
	case XK_Prior:
		if(!prev)
			return;
		sel = curr = prev;
		calcoffsets();
		break;
	case XK_Return:
	case XK_KP_Enter:
		puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
		ret = EXIT_SUCCESS;
		running = False;
	case XK_Right:
		if(text[cursor] != '\0') {
			cursor = nextrune(+1);
            break;
		}
		if(lines > 0)
			return;
		/* fallthrough */
	case XK_Down:
		if(sel && sel->right && (sel = sel->right) == next) {
			curr = next;
			calcoffsets();
		}
		break;
	case XK_Tab:
		if(!sel)
			return;
		strncpy(text, sel->text, sizeof text);
		cursor = strlen(text);
		match();
		break;
	}
	drawmenu();
}
Exemple #8
0
void handle_key(XKeyEvent ke) {
	FcChar8 buf[32];
	int len, n;
	KeySym keysym = NoSymbol;
	Status status;
	option *o;

	len = XmbLookupString(xic, &ke, buf, sizeof(buf),
	                      &keysym, &status);
	if (status == XBufferOverflow)
		return;

	if (ke.state & ControlMask) {
		switch(keysym) {
			case XK_a:
				keysym = XK_Home;
				break;
			case XK_e:
				keysym = XK_End;
				break;
			case XK_p:
				keysym = XK_Up;
				break;
			case XK_n:
				keysym = XK_Down;
				break;
			case XK_f:
				keysym = XK_Right;
				break;
			case XK_b:
				keysym = XK_Left;
				break;
			case XK_d:
				keysym = XK_Delete;
				break;
			case XK_bracketleft:
				keysym = XK_Escape;
				break;
			case XK_k:
				text[cursor] = '\0';
				break;
			case XK_u:
				for (n = cursor; cursor < MAX_LEN; cursor++) 
					text[cursor - n] = text[cursor];
				cursor = 0;
				update_valid_options();
				return;
		}
	}

	if (text_input) {
		switch(keysym) {
			default:
				if (!iscntrl(*buf)) {
					insert(buf, len);
					update_valid_options();
				}
				break;

			case XK_Delete:
				if (text[cursor] == '\0') break;
				cursor = nextrune(+1);
				
				/* Fall through to backspace */

			case XK_BackSpace:
				if (cursor == 0)
					exit(0);
				insert(NULL, nextrune(-1) - cursor);
				update_valid_options();
				break;

			case XK_Tab:
				copy_first();
				break;

			case XK_Left:
				if (cursor != 0)
					cursor = nextrune(-1);
				break;

			case XK_Right:
				if (text[cursor] != '\0') 
					cursor = nextrune(+1);
				break;

			case XK_Home:
				cursor = 0;
				break;
			
			case XK_End:
				while (text[cursor] != '\0')
					cursor = nextrune(+1);
		}
	}

	switch(keysym) {
		case XK_Up:
			if (valid->prev) 
				valid = valid->prev;
			else
				for (; 
				     valid && valid->next; 
				     valid = valid->next);
			break;
		case XK_Down:
			if (valid->next) 
				valid = valid->next;
			else
			 	for (; 
			 	     valid && valid->prev;
			 	     valid = valid->prev);
			break;
		case XK_Return:
			finish();
			break;
		case XK_Escape:
			exit(1);
			break;
	}

	if (exit_on_one) {
		for (n = 0, o = valid; o; o = o->next) n++;
		if (n == 1) finish();
	}
}
Exemple #9
0
static int lexclass(struct cstate *g)
{
	int type = L_CCLASS;
	int quoted, havesave, havedash;
	Rune save;

	newcclass(g);

	quoted = nextrune(g);
	if (!quoted && g->yychar == '^') {
		type = L_NCCLASS;
		quoted = nextrune(g);
	}

	havesave = havedash = 0;
	for (;;) {
		if (g->yychar == 0)
			die(g, "unterminated character class");
		if (!quoted && g->yychar == ']')
			break;

		if (!quoted && g->yychar == '-') {
			if (havesave) {
				if (havedash) {
					addrange(g, save, '-');
					havesave = havedash = 0;
				} else {
					havedash = 1;
				}
			} else {
				save = '-';
				havesave = 1;
			}
		} else if (quoted && strchr("DSWdsw", g->yychar)) {
			if (havesave) {
				addrange(g, save, save);
				if (havedash)
					addrange(g, '-', '-');
			}
			switch (g->yychar) {
			case 'd': addranges_d(g); break;
			case 's': addranges_s(g); break;
			case 'w': addranges_w(g); break;
			case 'D': addranges_D(g); break;
			case 'S': addranges_S(g); break;
			case 'W': addranges_W(g); break;
			}
			havesave = havedash = 0;
		} else {
			if (quoted) {
				if (g->yychar == 'b')
					g->yychar = '\b';
				else if (g->yychar == '0')
					g->yychar = 0;
				/* else identity escape */
			}
			if (havesave) {
				if (havedash) {
					addrange(g, save, g->yychar);
					havesave = havedash = 0;
				} else {
					addrange(g, save, save);
					save = g->yychar;
				}
			} else {
				save = g->yychar;
				havesave = 1;
			}
		}

		quoted = nextrune(g);
	}

	if (havesave) {
		addrange(g, save, save);
		if (havedash)
			addrange(g, '-', '-');
	}

	return type;
}
Exemple #10
0
void keypress(Input *i, XKeyEvent *ev)
{
	char buf[32];
	int len;
	KeySym ksym = NoSymbol;
	Status status;

	len = XmbLookupString(ic, ev, buf, sizeof(buf), &ksym, &status);
	if (status == XBufferOverflow)
		return;
	if (ev->state & ControlMask)
		switch (ksym) {
		case XK_a: ksym = XK_Home;      break;
		case XK_b: ksym = XK_Left;      break;
		case XK_d: ksym = XK_Delete;    break;
		case XK_e: ksym = XK_End;       break;
		case XK_f: ksym = XK_Right;     break;
		case XK_h: ksym = XK_BackSpace; break;
		case XK_i: ksym = XK_Tab;       break;
		case XK_j:
		case XK_m: ksym = XK_Return;    break;
		default:
			return;
		}
	else if (ev->state & Mod1Mask)
		switch (ksym) {
		case XK_g: ksym = XK_Home; break;
		case XK_G: ksym = XK_End;  break;
		default:
			return;
		}
	switch (ksym) {
	default:
		if (!iscntrl(*buf))
			insert(i, buf, len);
		break;
	case XK_Delete:
		if (i->text[i->cursor] == 0)
			return;
		i->cursor = nextrune(i, +1);
		/* fallthrough */
	case XK_BackSpace:
		if (i->cursor == 0)
			return;
		insert(i, NULL, nextrune(i, -1) - i->cursor);
		break;
	case XK_Home:
		if (i->cursor == 0)
			return;
		i->cursor = 0;
		break;
	case XK_End:
		if (i->text[i->cursor] == 0)
			return;
		i->cursor = strlen(i->text);
		break;
	case XK_Left:
		if (i->cursor == 0)
			return;
		i->cursor = nextrune(i, -1);
		break;
	case XK_Right:
		if (i->text[i->cursor] == 0)
			return;
		i->cursor = nextrune(i, +1);
		break;
	case XK_Return:
	case XK_KP_Enter:
		if (!i->next) {
			running = False;
			return;
		}
		/* fallthrough */
	case XK_Tab:
		XSetInputFocus(dpy, i->next ? i->next->win : inputs->win,
				RevertToPointerRoot, CurrentTime);
		return;
	}
	drawinput(i);
}
Exemple #11
0
static int
run(void) {
	char buf[32];
	char c;

	for (;;) {
		xread(0, &c, 1);
		memset(buf, '\0', sizeof buf);
		buf[0] = c;
		switch_top:
		switch(c) {
		case CONTROL('['):
			xread(0, &c, 1);
			esc_switch_top:
			switch(c) {
			case CONTROL('['): /* ESC, need to press twice due to console limitations */
				c = CONTROL('C');
				goto switch_top;
			case '[':
				xread(0, &c, 1);
				switch(c) {
				case '1': /* Home */
				case '7':
				case 'H':
					if (c != 'H') xread(0, &c, 1); /* Remove trailing '~' from stdin */
					c = CONTROL('A');
					goto switch_top;
				case '2': /* Insert */
					xread(0, &c, 1); /* Remove trailing '~' from stdin */
					c = CONTROL('Y');
					goto switch_top;
				case '3': /* Delete */
					xread(0, &c, 1); /* Remove trailing '~' from stdin */
					c = CONTROL('D');
					goto switch_top;
				case '4': /* End */
				case '8':
				case 'F':
					if (c != 'F') xread(0, &c, 1); /* Remove trailing '~' from stdin */
					c = CONTROL('E');
					goto switch_top;
				case '5': /* PageUp */
					xread(0, &c, 1); /* Remove trailing '~' from stdin */
					c = CONTROL('V');
					goto switch_top;
				case '6': /* PageDown */
					xread(0, &c, 1); /* Remove trailing '~' from stdin */
					c = 'v';
					goto esc_switch_top;
				case 'A': /* Up arrow */
					c = CONTROL('P');
					goto switch_top;
				case 'B': /* Down arrow */
					c = CONTROL('N');
					goto switch_top;
				case 'C': /* Right arrow */
					c = CONTROL('F');
					goto switch_top;
				case 'D': /* Left arrow */
					c = CONTROL('B');
					goto switch_top;
				}
				break;
			case 'b':
				while (cursor > 0 && text[nextrune(-1)] == ' ')
					cursor = nextrune(-1);
				while (cursor > 0 && text[nextrune(-1)] != ' ')
					cursor = nextrune(-1);
				break;
			case 'f':
				while (text[cursor] != '\0' && text[nextrune(+1)] == ' ')
					cursor = nextrune(+1);
				if (text[cursor] != '\0') {
					do {
						cursor = nextrune(+1);
					} while (text[cursor] != '\0' && text[cursor] != ' ');
				}
				break;
			case 'd':
				while (text[cursor] != '\0' && text[nextrune(+1)] == ' ') {
					cursor = nextrune(+1);
					insert(NULL, nextrune(-1) - cursor);
				}
				if (text[cursor] != '\0') {
					do {
						cursor = nextrune(+1);
						insert(NULL, nextrune(-1) - cursor);
					} while (text[cursor] != '\0' && text[cursor] != ' ');
				}
				break;
			case 'v':
				if (!next)
					break;
				sel = curr = next;
				calcoffsets();
				break;
			default:
				break;
			}
			break;
		case CONTROL('C'):
			return EXIT_FAILURE;
		case CONTROL('M'): /* Return */
		case CONTROL('J'):
			if (sel) strncpy(text, sel->text, sizeof(text)-1); /* Complete the input first, when hitting return */
			cursor = strlen(text);
			match();
			drawmenu();
			/* fallthrough */
		case CONTROL(']'):
		case CONTROL('\\'): /* These are usually close enough to RET to replace Shift+RET, again due to console limitations */
			puts(text);
			return EXIT_SUCCESS;
		case CONTROL('A'):
			if (sel == matches) {
				cursor = 0;
				break;
			}
			sel = curr = matches;
			calcoffsets();
			break;
		case CONTROL('E'):
			if (text[cursor] != '\0') {
				cursor = strlen(text);
				break;
			}
			if (next) {
				curr = matchend;
				calcoffsets();
				curr = prev;
				calcoffsets();
				while(next && (curr = curr->right))
					calcoffsets();
			}
			sel = matchend;
			break;
		case CONTROL('B'):
			if (cursor > 0 && (!sel || !sel->left || lines > 0)) {
				cursor = nextrune(-1);
				break;
			}
			/* fallthrough */
		case CONTROL('P'):
			if (sel && sel->left && (sel = sel->left)->right == curr) {
				curr = prev;
				calcoffsets();
			}
			break;
		case CONTROL('F'):
			if (text[cursor] != '\0') {
				cursor = nextrune(+1);
				break;
			}
			/* fallthrough */
		case CONTROL('N'):
			if (sel && sel->right && (sel = sel->right) == next) {
				curr = next;
				calcoffsets();
			}
			break;
		case CONTROL('D'):
			if (text[cursor] == '\0')
				break;
			cursor = nextrune(+1);
			/* fallthrough */
		case CONTROL('H'):
		case CONTROL('?'): /* Backspace */
			if (cursor == 0)
				break;
			insert(NULL, nextrune(-1) - cursor);
			break;
		case CONTROL('I'): /* TAB */
			if (!sel)
				break;
			strncpy(text, sel->text, sizeof text);
			cursor = strlen(text);
			match();
			break;
		case CONTROL('K'):
			text[cursor] = '\0';
			match();
			break;
		case CONTROL('U'):
			insert(NULL, 0 - cursor);
			break;
		case CONTROL('W'):
			while (cursor > 0 && text[nextrune(-1)] == ' ')
				insert(NULL, nextrune(-1) - cursor);
			while (cursor > 0 && text[nextrune(-1)] != ' ')
				insert(NULL, nextrune(-1) - cursor);
			break;
		case CONTROL('V'):
			if (!prev)
				break;
			sel = curr = prev;
			calcoffsets();
			break;
		default:
			if (!iscntrl(*buf))
				insert(buf, strlen(buf));
			break;
		}
		drawmenu();
	}
}
Exemple #12
0
void
keypress(XKeyEvent *ev) {
    char buf[32];
    size_t len;
    KeySym ksym;

    len = strlen(text);
    XLookupString(ev, buf, sizeof buf, &ksym, NULL);
    if(ev->state & ControlMask) {
        switch(tolower(ksym)) {
        default:
            return;
        case XK_a:
            ksym = XK_Home;
            break;
        case XK_b:
            ksym = XK_Left;
            break;
        case XK_c:
            ksym = XK_Escape;
            break;
        case XK_d:
            ksym = XK_Delete;
            break;
        case XK_e:
            ksym = XK_End;
            break;
        case XK_f:
            ksym = XK_Right;
            break;
        case XK_h:
            ksym = XK_BackSpace;
            break;
        case XK_i:
            ksym = XK_Tab;
            break;
        case XK_j:
            ksym = XK_Return;
            break;
        case XK_k:  /* delete right */
            text[cursor] = '\0';
            match();
            break;
        case XK_n:
            ksym = XK_Down;
            break;
        case XK_p:
            ksym = XK_Up;
            break;
        case XK_u:  /* delete left */
            insert(NULL, 0 - cursor);
            break;
        case XK_w:  /* delete word */
            while(cursor > 0 && text[nextrune(-1)] == ' ')
                insert(NULL, nextrune(-1) - cursor);
            while(cursor > 0 && text[nextrune(-1)] != ' ')
                insert(NULL, nextrune(-1) - cursor);
            break;
        case XK_y:  /* paste selection */
            XConvertSelection(dc->dpy, XA_PRIMARY, utf8, utf8, win, CurrentTime);
            return;
        }
    }
    switch(ksym) {
    default:
        if(!iscntrl(*buf))
            insert(buf, strlen(buf));
        break;
    case XK_Delete:
        if(cursor == len)
            return;
        cursor = nextrune(+1);
    case XK_BackSpace:
        if(cursor > 0)
            insert(NULL, nextrune(-1) - cursor);
        break;
    case XK_End:
        if(cursor < len) {
            cursor = len;
            break;
        }
        while(next) {
            sel = curr = next;
            calcoffsets();
        }
        while(sel && sel->right)
            sel = sel->right;
        break;
    case XK_Escape:
        exit(EXIT_FAILURE);
    case XK_Home:
        if(sel == matches) {
            cursor = 0;
            break;
        }
        sel = curr = matches;
        calcoffsets();
        break;
    case XK_Left:
        if(cursor > 0 && (!sel || !sel->left || lines > 0)) {
            cursor = nextrune(-1);
            break;
        }
        else if(lines > 0)
            return;
    case XK_Up:
        if(sel && sel->left && (sel = sel->left)->right == curr) {
            curr = prev;
            calcoffsets();
        }
        break;
    case XK_Next:
        if(!next)
            return;
        sel = curr = next;
        calcoffsets();
        break;
    case XK_Prior:
        if(!prev)
            return;
        sel = curr = prev;
        calcoffsets();
        break;
    case XK_Return:
    case XK_KP_Enter:
        handle_return((sel && !(ev->state & ShiftMask)) ? sel->text : text);
    case XK_Right:
        if(cursor < len) {
            cursor = nextrune(+1);
            break;
        }
        else if(lines > 0)
            return;
    case XK_Down:
        if(sel && sel->right && (sel = sel->right) == next) {
            curr = next;
            calcoffsets();
        }
        break;
    case XK_Tab:
        if(!sel)
            return;
        strncpy(text, sel->text, sizeof text);
        cursor = strlen(text);
        match();
        break;
    }
    drawmenu();
}