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