1 /*
2  * Copyright (C) 2000, 2001, 2002, 2003, 2004 Shawn Betts <sabetts@vcn.bc.ca>
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License as published by the Free
6  * Software Foundation; either version 2 of the License, or (at your option)
7  * any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12  * more details.
13  *
14  * You should have received a copy of the GNU General Public License along with
15  * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16  * Place, Suite 330, Boston, MA 02111-1307 USA.
17  */
18 
19 /* Needed on Linux for strcasestr */
20 #define _GNU_SOURCE
21 #include <string.h>
22 
23 #include "sdorfehs.h"
24 
25 rp_completions *
completions_new(completion_fn list_fn,enum completion_styles style)26 completions_new(completion_fn list_fn, enum completion_styles style)
27 {
28 	rp_completions *c;
29 
30 	c = xmalloc(sizeof(rp_completions));
31 
32 	INIT_LIST_HEAD(&c->completion_list);
33 	c->complete_fn = list_fn;
34 	c->last_match = NULL;
35 	c->partial = NULL;
36 	c->virgin = 1;
37 	c->style = style;
38 
39 	return c;
40 }
41 
42 void
completions_free(rp_completions * c)43 completions_free(rp_completions *c)
44 {
45 	struct sbuf *cur;
46 	struct list_head *tmp, *iter;
47 
48 	/* Clear our list */
49 	list_for_each_safe_entry(cur, iter, tmp, &c->completion_list, node) {
50 		list_del(&cur->node);
51 		sbuf_free(cur);
52 	}
53 
54 	/* Free the partial string. */
55 	free(c->partial);
56 
57 	free(c);
58 }
59 
60 static void
completions_assign(rp_completions * c,struct list_head * new_list)61 completions_assign(rp_completions *c, struct list_head *new_list)
62 {
63 	struct sbuf *cur;
64 	struct list_head *tmp, *iter;
65 
66 	/* Clear our list */
67 	list_for_each_safe_entry(cur, iter, tmp, &c->completion_list, node) {
68 		list_del(&cur->node);
69 		sbuf_free(cur);
70 	}
71 
72 	/*
73 	 * splice the list into completion_list. Note that we SHOULDN'T free
74 	 * new_list, because they share the same memory.
75 	 */
76 	INIT_LIST_HEAD(&c->completion_list);
77 	list_splice(new_list, &c->completion_list);
78 
79 	list_first(c->last_match, &c->completion_list, node);
80 }
81 
82 static void
completions_update(rp_completions * c,char * partial)83 completions_update(rp_completions *c, char *partial)
84 {
85 	struct list_head *new_list;
86 
87 	new_list = c->complete_fn(partial);
88 
89 	c->virgin = 0;
90 	free(c->partial);
91 	c->partial = xstrdup(partial);
92 
93 	completions_assign(c, new_list);
94 
95 	/* Free the head structure for our list. */
96 	free(new_list);
97 }
98 
99 /*
100  * Return true if completion is an alternative for partial string, given the
101  * style used.
102  */
103 static int
completions_match(rp_completions * c,char * completion,char * partial)104 completions_match(rp_completions *c, char *completion, char *partial)
105 {
106 	int match = 0;
107 
108 	switch (c->style) {
109 	case BASIC:
110 		match = str_comp(completion, partial, strlen(partial));
111 		break;
112 	case SUBSTRING:
113 		match = (strcasestr(completion, partial) != NULL);
114 		break;
115 	}
116 
117 	return match;
118 }
119 
120 static char *
completions_prev_match(rp_completions * c)121 completions_prev_match(rp_completions *c)
122 {
123 	struct sbuf *cur;
124 
125 	/*
126 	 * search forward from our last match through the list looking for
127 	 * another match.
128 	 */
129 	for (cur = list_prev_entry(c->last_match, &c->completion_list, node);
130 	    cur != c->last_match;
131 	    cur = list_prev_entry(cur, &c->completion_list, node)) {
132 		if (completions_match(c, sbuf_get(cur), c->partial)) {
133 			/*
134 			 * We found a match so update our last_match pointer
135 			 * and return the string.
136 			 */
137 			c->last_match = cur;
138 			return sbuf_get(cur);
139 		}
140 	}
141 
142 	return NULL;
143 }
144 
145 static char *
completions_next_match(rp_completions * c)146 completions_next_match(rp_completions *c)
147 {
148 	struct sbuf *cur;
149 
150 	/*
151 	 * search forward from our last match through the list looking for
152 	 * another match.
153 	 */
154 	for (cur = list_next_entry(c->last_match, &c->completion_list, node);
155 	    cur != c->last_match;
156 	    cur = list_next_entry(cur, &c->completion_list, node)) {
157 		if (completions_match(c, sbuf_get(cur), c->partial)) {
158 			/*
159 			 * We found a match so update our last_match pointer
160 			 * and return the string.
161 			 */
162 			c->last_match = cur;
163 			return sbuf_get(cur);
164 		}
165 	}
166 
167 	return NULL;
168 }
169 
170 /* Return a completed string that starts with partial. */
171 char *
completions_complete(rp_completions * c,char * partial,int direction)172 completions_complete(rp_completions *c, char *partial, int direction)
173 {
174 	if (c->virgin) {
175 		completions_update(c, partial);
176 
177 		/*
178 		 * Since it's never been completed on and c->last_match points
179 		 * to the first element of the list which may be a match. So
180 		 * check it. FIXME: This is a bit of a hack.
181 		 */
182 		if (c->last_match == NULL)
183 			return NULL;
184 
185 		/*
186 		 * c->last_match contains the first match in the forward
187 		 * direction. So if we're looking for the previous match, then
188 		 * check the previous element from last_match.
189 		 */
190 		if (direction == COMPLETION_PREVIOUS)
191 			c->last_match = list_prev_entry(c->last_match,
192 			    &c->completion_list, node);
193 
194 		/* Now check if last_match is a match for partial. */
195 		if (completions_match(c, sbuf_get(c->last_match), c->partial))
196 			return sbuf_get(c->last_match);
197 	}
198 	if (c->last_match == NULL)
199 		return NULL;
200 
201 	/* Depending on the direction, find our "next" match. */
202 	if (direction == COMPLETION_NEXT)
203 		return completions_next_match(c);
204 
205 	/* Otherwise get the previous match */
206 	return completions_prev_match(c);
207 }
208