1/*
2 * Command line editing and history wrapper for readline
3 * Copyright (c) 2010, Jouni Malinen <j@w1.fi>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 as
7 * published by the Free Software Foundation.
8 *
9 * Alternatively, this software may be distributed under the terms of BSD
10 * license.
11 *
12 * See README and COPYING for more details.
13 */
14
15#include "includes.h"
16#include <readline/readline.h>
17#include <readline/history.h>
18
19#include "common.h"
20#include "eloop.h"
21#include "edit.h"
22
23
24static void *edit_cb_ctx;
25static void (*edit_cmd_cb)(void *ctx, char *cmd);
26static void (*edit_eof_cb)(void *ctx);
27static char ** (*edit_completion_cb)(void *ctx, const char *cmd, int pos) =
28	NULL;
29
30static char **pending_completions = NULL;
31
32
33static void readline_free_completions(void)
34{
35	int i;
36	if (pending_completions == NULL)
37		return;
38	for (i = 0; pending_completions[i]; i++)
39		os_free(pending_completions[i]);
40	os_free(pending_completions);
41	pending_completions = NULL;
42}
43
44
45static char * readline_completion_func(const char *text, int state)
46{
47	static int pos = 0;
48	static size_t len = 0;
49
50	if (pending_completions == NULL) {
51		rl_attempted_completion_over = 1;
52		return NULL;
53	}
54
55	if (state == 0) {
56		pos = 0;
57		len = os_strlen(text);
58	}
59	for (; pending_completions[pos]; pos++) {
60		if (strncmp(pending_completions[pos], text, len) == 0)
61			return strdup(pending_completions[pos++]);
62	}
63
64	rl_attempted_completion_over = 1;
65	return NULL;
66}
67
68
69static char ** readline_completion(const char *text, int start, int end)
70{
71	readline_free_completions();
72	if (edit_completion_cb)
73		pending_completions = edit_completion_cb(edit_cb_ctx,
74							 rl_line_buffer, end);
75	return rl_completion_matches(text, readline_completion_func);
76}
77
78
79static void edit_read_char(int sock, void *eloop_ctx, void *sock_ctx)
80{
81	rl_callback_read_char();
82}
83
84
85static void trunc_nl(char *str)
86{
87	char *pos = str;
88	while (*pos != '\0') {
89		if (*pos == '\n') {
90			*pos = '\0';
91			break;
92		}
93		pos++;
94	}
95}
96
97
98static void readline_cmd_handler(char *cmd)
99{
100	if (cmd && *cmd) {
101		HIST_ENTRY *h;
102		while (next_history())
103			;
104		h = previous_history();
105		if (h == NULL || os_strcmp(cmd, h->line) != 0)
106			add_history(cmd);
107		next_history();
108	}
109	if (cmd == NULL) {
110		edit_eof_cb(edit_cb_ctx);
111		return;
112	}
113	trunc_nl(cmd);
114	edit_cmd_cb(edit_cb_ctx, cmd);
115}
116
117
118int edit_init(void (*cmd_cb)(void *ctx, char *cmd),
119	      void (*eof_cb)(void *ctx),
120	      char ** (*completion_cb)(void *ctx, const char *cmd, int pos),
121	      void *ctx, const char *history_file)
122{
123	edit_cb_ctx = ctx;
124	edit_cmd_cb = cmd_cb;
125	edit_eof_cb = eof_cb;
126	edit_completion_cb = completion_cb;
127
128	rl_attempted_completion_function = readline_completion;
129	if (history_file) {
130		read_history(history_file);
131		stifle_history(100);
132	}
133
134	eloop_register_read_sock(STDIN_FILENO, edit_read_char, NULL, NULL);
135
136	rl_callback_handler_install("> ", readline_cmd_handler);
137
138	return 0;
139}
140
141
142void edit_deinit(const char *history_file,
143		 int (*filter_cb)(void *ctx, const char *cmd))
144{
145	rl_callback_handler_remove();
146	readline_free_completions();
147
148	eloop_unregister_read_sock(STDIN_FILENO);
149
150	if (history_file) {
151		/* Save command history, excluding lines that may contain
152		 * passwords. */
153		HIST_ENTRY *h;
154		history_set_pos(0);
155		while ((h = current_history())) {
156			char *p = h->line;
157			while (*p == ' ' || *p == '\t')
158				p++;
159			if (filter_cb && filter_cb(edit_cb_ctx, p)) {
160				h = remove_history(where_history());
161				if (h) {
162					os_free(h->line);
163					free(h->data);
164					os_free(h);
165				} else
166					next_history();
167			} else
168				next_history();
169		}
170		write_history(history_file);
171	}
172}
173
174
175void edit_clear_line(void)
176{
177}
178
179
180void edit_redraw(void)
181{
182	rl_on_new_line();
183	rl_redisplay();
184}
185