summaryrefslogtreecommitdiff
path: root/vchat-user.c
diff options
context:
space:
mode:
authorerdgeist <>2012-02-27 00:06:17 +0000
committererdgeist <>2012-02-27 00:06:17 +0000
commit7dbafe3f0fa465949ef66d800a8cbd0b191c9519 (patch)
tree45ad89dfee0154b76d2473a3d71ffbb0222bf7b4 /vchat-user.c
parentf434f9cd4eabfcad3a90711494febbfd89e4ed5f (diff)
Complete rewrite of user handling. HEADS UP\!
Diffstat (limited to 'vchat-user.c')
-rwxr-xr-xvchat-user.c660
1 files changed, 299 insertions, 361 deletions
diff --git a/vchat-user.c b/vchat-user.c
index 4b44080..cd0df14 100755
--- a/vchat-user.c
+++ b/vchat-user.c
@@ -1,426 +1,364 @@
1/* 1/*
2 * vchat-client - alpha version 2 * vchat-client
3 * vchat-user.c - functions working with the userlist 3
4 * 4*/
5 * Copyright (C) 2001 Andreas Kotes <count@flatline.de> 5
6 * 6#include <stdint.h>
7 * This program is free software. It can be redistributed and/or modified,
8 * provided that this copyright notice is kept intact. This program is
9 * distributed in the hope that it will be useful, but without any warranty;
10 * without even the implied warranty of merchantability or fitness for a
11 * particular purpose. In no event shall the copyright holder be liable for
12 * any direct, indirect, incidental or special damages arising in any way out
13 * of the use of this software.
14 *
15 */
16
17/* general includes */
18#include <stdlib.h> 7#include <stdlib.h>
8#include <strings.h>
19#include <stdio.h> 9#include <stdio.h>
20#include <string.h> 10#include <sys/time.h>
21#include <sys/types.h>
22#include <regex.h> 11#include <regex.h>
23#include <time.h>
24#include <readline/readline.h> 12#include <readline/readline.h>
25#include "vchat.h"
26 13
27struct user 14#include "vchat.h"
28{ 15#include "vchat-user.h"
29 char *nick; /* nick of user */
30 int chan; /* channel user is on */
31 int chan_valid; /* are we sure he is? */
32 int client_pv; /* client protocol version */
33 int messaged; /* did we message with this user? */
34 struct user *next;/* next user in linked list */
35};
36 16
37/* version of this module */ 17/* version of this module */
38char *vchat_us_version = "$Id$"; 18char *vchat_us_version = "$Id$";
39 19
40/* externally used variables */ 20typedef struct
41/* current nick */
42char *nick = NULL;
43/* current channel */
44int chan = 0;
45/* userlist */
46user *nicks = NULL;
47
48/* add user to userlist */
49void
50ul_add (char *name, int ignored)
51{
52 user *tmp = NULL;
53
54 /* no list? create one */
55 if (!nicks)
56 {
57 nicks = malloc (sizeof (user));
58 memset(nicks,0,sizeof(user));
59 nicks->nick = strdup(name);
60 nicks->chan = 0; /* users default in channel 0 */
61 nicks->chan_valid = 0;
62 nicks->next = NULL;
63 }
64 else
65 {
66 /* travel list until end */
67 tmp = nicks;
68 while (tmp)
69 {
70 /* it is this user? return */
71 if (!strcmp (name, tmp->nick))
72 return;
73 /* is there a next user? */
74 if (tmp->next)
75 tmp = tmp->next;
76 else
77 {
78 /* create one */
79 tmp->next = malloc (sizeof (user));
80 tmp = tmp->next;
81 memset(tmp,0,sizeof(user));
82 tmp->nick = strdup(name);
83 tmp->chan = 0;
84 tmp->chan_valid = 0;
85 tmp->next = NULL;
86 tmp = NULL;
87 }
88 }
89 }
90#ifndef OLDREADLINE
91 rl_last_func = NULL;
92#endif
93}
94
95/* delete user from userlist */
96void
97ul_del (char *name, int ignored)
98{ 21{
99 user *tmp = NULL, *ltmp = NULL; 22 char *nick;
100 23 enum { UL_NONE = 0x00, UL_ME = 0x01, UL_IN_MY_CHAN = 0x02, UL_NOT_IN_LIST = 0x04 } flags;
101 /* is it this client? return */ 24 uint64_t last_public;
102 if (nick && !strcmp (nick, name)) 25 uint64_t last_private;
103 return; 26} user;
104 /* no list? return */ 27static user *g_users; //< all users, incl self
105 if (!nicks) 28static size_t g_users_count; //< number of users in list
106 return; 29static char *g_nick; //< own nick
107 /* the user on top of list? */ 30static int g_channel; //< own channel
108 if (!strcmp (name, nicks->nick)) 31int ul_case_first = 0;
109 { 32
110 /* remove user and copy next in list */ 33static int ul_nick_lookup( const char *nick, int *exact_match ) {
111 tmp = nicks->next; 34 int i;
112 free (nicks); 35
113 nicks = tmp; 36 *exact_match = 1;
114 return; 37 for( i=0; i<g_users_count; ++i )
115 } 38 if( !strcasecmp( g_users[i].nick, nick ) )
116 /* travel through list, skip first entry */ 39 return i;
117 ltmp = nicks; 40 *exact_match = 0;
118 tmp = nicks->next; 41 return i;
119 while (tmp)
120 {
121 /* is it this user? */
122 if (!strcmp (name, tmp->nick))
123 {
124 /* hook next to last, discard this */
125 ltmp->next = tmp->next;
126 free (tmp);
127 return;
128 }
129 /* advance in list */
130 ltmp = tmp;
131 tmp = tmp->next;
132 }
133#ifndef OLDREADLINE
134 rl_last_func = NULL;
135#endif
136} 42}
137 43
138/* let user join a channel */ 44static int64_t ul_now() {
139void 45 struct timeval now;
140ul_join (char *name, int channel) 46 gettimeofday(&now,(struct timezone*) 0);
141{ 47 return ((uint64_t)now.tv_sec * 1000) + ((uint64_t)now.tv_usec / 1000 );
142 /* is it this client? handle and return */
143 if (nick && !strcmp (nick, name))
144 {
145 ownjoin (channel);
146 return;
147 } else ul_moveuser(name,channel);
148#ifndef OLDREADLINE
149 rl_last_func = NULL;
150#endif
151} 48}
152 49
153user * 50/* own nick and channel setters/getters */
154ul_finduser (char *name) { 51void own_nick_set( char *nick ) {
155 user *tmp = nicks; 52 if( nick ) {
156 snprintf( tmpstr, TMPSTRSIZE, "%s:", name); 53 int base;
157 54 if( g_nick )
158 /* search user */ 55 base = ul_rename( g_nick, nick );
159 while (tmp) 56 else
160 { 57 base = ul_add( nick, 0 );
161 /* is it this user? */ 58 if( base >= 0 ) {
162 if (!strcmp (name, tmp->nick) || !strcmp(tmpstr, tmp->nick)) 59 g_users[base].flags |= UL_ME;
163 { 60 g_nick = g_users[base].nick;
164 return tmp;
165 }
166 /* advance in list */
167 tmp = tmp->next;
168 } 61 }
169 return NULL; 62 } else
170} 63 ul_del( g_nick );
171 64
172char * 65 setstroption(CF_NICK, nick);
173ul_matchuser( char *regex) { 66}
174 user *tmp = nicks;
175 char *dest = tmpstr;
176 regex_t preg;
177 67
178 *dest = 0; 68void own_channel_set( int channel ) {
179 if( !regcomp( &preg, regex, REG_ICASE | REG_EXTENDED | REG_NEWLINE)) { 69 if( channel != g_channel ) {
180 while( tmp ) { 70 /* Remove all users from my chan, will be re-set on join message */
181 /* does the username match? */ 71 int i;
182 if( !regexec( &preg, tmp->nick, 0, NULL, 0)) /* append username to list */ 72 for( i=0; i<g_users_count; ++i )
183 dest += snprintf ( dest, 256, " %s", tmp->nick); 73 g_users[i].flags &= ~UL_IN_MY_CHAN;
184 tmp = tmp->next;
185 }
186 } 74 }
187 regfree( &preg ); 75
188 return tmpstr; 76 g_channel = channel;
189} 77}
190 78
191static void 79char const *own_nick_get( ) {
192ul_usertofront( user *who ) { 80 return g_nick;
193 user *tmp = nicks; 81}
194 82
195 while( tmp->next && ( tmp->next != who ) ) 83int own_nick_check( char *nick ) {
196 tmp = tmp->next; 84 if( !g_nick ) return -1;
85 return !strncasecmp(g_nick,nick,strlen(g_nick) );
86}
197 87
198 if( tmp->next == who ) { 88int own_channel_get( ) {
199 tmp->next = tmp->next->next; 89 return g_channel;
200 who->next = nicks;
201 nicks = who;
202 }
203#ifndef OLDREADLINE
204 rl_last_func = NULL;
205#endif
206} 90}
207 91
208void 92/* Add/remove/rename */
209ul_msgto (char *name) { 93int ul_add(char *name, int in_my_chan_flag ) {
210 user *tmp = ul_finduser(name); 94
95 /* Test if user is already known */
96 int exact_match, base = ul_nick_lookup( name, &exact_match );
97 if( !exact_match ) {
98 /* Make space for new user */
99 user * new_users = realloc( g_users, sizeof( user ) * ( 1 + g_users_count ) );
100 if( !new_users ) return -1;
101
102 /* Copy the tail */
103 g_users = new_users;
104 memmove( g_users + base + 1, g_users + base, ( g_users_count - base ) * sizeof( user ) );
105 g_users[base].nick = strdup( name );
106 g_users[base].flags = UL_NONE;
107 g_users[base].last_public = 0;
108 g_users[base].last_private = 0;
109
110 g_users_count++;
111 }
211 112
212 if (tmp) { 113 g_users[base].flags &= ~UL_NOT_IN_LIST;
213 tmp->messaged |= 1; 114 switch( in_my_chan_flag ) {
214 ul_usertofront( tmp ); 115 case 1: g_users[base].flags |= UL_IN_MY_CHAN; break;
116 case 0: g_users[base].flags &= ~UL_IN_MY_CHAN; break;
117 case -1: default: break;
215 } 118 }
119
120 return base;
216} 121}
217 122
218void 123int ul_del(char *name) {
219ul_msgfrom (char *name) { 124 /* Test if user is already known */
220 user *tmp = ul_finduser(name); 125 int exact_match, base = ul_nick_lookup( name, &exact_match );
126 if( !exact_match ) return -1;
221 127
222 if (tmp) { 128 /* Release the name buffer */
223 tmp->messaged |= 2; 129 free( g_users[base].nick );
224 ul_usertofront( tmp ); 130 if( g_users[base].flags & UL_ME ) g_nick = 0;
225 }
226}
227 131
228/* set channel of user */ 132 /* Copy the tail */
229void 133 memmove( g_users + base, g_users + base + 1, ( g_users_count - base - 1 ) * sizeof( user ) );
230ul_moveuser (char *name, int channel) {
231 user *tmp = ul_finduser(name);
232 134
233 if (tmp) { 135 /* Shrink user list, realloc to a smaller size never fails */
234 /* store channel information and mark it valid */ 136 g_users = realloc( g_users, sizeof( user ) * --g_users_count );
235 tmp->chan = channel; 137 return 0;
236 tmp->chan_valid = 1;
237 }
238#ifndef OLDREADLINE
239 rl_last_func = NULL;
240#endif
241} 138}
242 139
243/* let user leave a channel */ 140int ul_rename(char *oldname, char *newname) {
244void 141 /* Ensure user */
245ul_leave (char *name, int channel) 142 int base = ul_add( oldname, -1 );
246{ 143 if( base >= 0 ) {
247 user *tmp = ul_finduser(name); 144 free( g_users[base].nick );
248 /* is it this client? handle and return */ 145 g_users[base].nick = strdup( newname );
249 if (nick && !strcmp (nick, name)) 146 if( g_users[base].flags & UL_ME )
250 { 147 g_nick = g_users[base].nick;
251 ownleave (channel); 148 if( g_users[base].flags & UL_IN_MY_CHAN )
252 return; 149 ul_public_action(newname);
253 } 150 }
151 return base;
152}
254 153
255 if (tmp) 154void ul_clear() {
256 { 155 int i;
257 /* mark channel information invalid */ 156 for( i=0; i<g_users_count; ++i )
258 tmp->chan_valid = 0; 157 free( g_users[i].nick );
259 return; 158 free( g_users );
260 } 159 g_nick = 0;
261#ifndef OLDREADLINE
262 rl_last_func = NULL;
263#endif
264} 160}
265 161
266/* let user change nick */ 162void ul_rebuild_list( ) {
267void 163 int i;
268ul_nickchange (char *oldnick, char *newnick) 164 for( i=0; i<g_users_count; ++i )
269{ 165 g_users[i].flags |= UL_NOT_IN_LIST;
270 user *tmp = ul_finduser(oldnick);
271 /* is it this client? handle and return */
272 if (nick && !strcmp (nick, oldnick))
273 {
274 ownnickchange (newnick);
275 return;
276 }
277 if (tmp)
278 {
279 /* exchange nickname */
280 free (tmp->nick);
281 tmp->nick = strdup (newnick);
282 return;
283 }
284#ifndef OLDREADLINE
285 rl_last_func = NULL;
286#endif
287} 166}
288 167
289/* clear userlist */ 168void ul_clean() {
290void 169 int i;
291ul_clear (void) 170 for( i=0; i<g_users_count; ++i ) {
292{ 171 if( g_users[i].flags & UL_NOT_IN_LIST ) {
293 user *tmp = nicks, *tmp2; 172 ul_del( g_users[i].nick );
294 /* walk list */ 173 --i;
295 while (tmp)
296 {
297 /* store next, delete this */
298 tmp2 = tmp->next;
299 free (tmp->nick);
300 free (tmp);
301 /* advance */
302 tmp = tmp2;
303 } 174 }
304 /* mark list empty */ 175 }
305 nicks = NULL;
306#ifndef OLDREADLINE
307 rl_last_func = NULL;
308#endif
309} 176}
310 177
311int ulnc_casenick(user *tmp, const char *text, int len, int value) { 178/* Seting state */
312 return (!strncmp(tmp->nick, text, len)); 179void ul_leave_chan(char *name) {
180 /* Ensure user and kick him off the channel */
181 ul_add(name, 0);
313} 182}
314 183
315int ulnc_ncasenick(user *tmp, const char *text, int len, int value) { 184void ul_enter_chan(char *name) {
316 return (!strncasecmp(tmp->nick, text, len)); 185 /* Ensure user and put him on the channel */
186 int base = ul_add(name, 1);
187 if( base >= 0 )
188 ul_public_action(name);
189
190 /* Reflect in UI */
191 if( own_nick_check( name ) )
192 ownjoin( g_channel );
317} 193}
318 194
319char * 195void ul_private_action(char *name) {
320ulnc_complete (const char *text, int state, int value, int (*checkfn)(user *,const char *,int,int)) { 196 /* Ensure user and keep channel state */
321 static int len; 197 int base = ul_add(name, -1);
322 static user *tmp; 198 if( base >= 0 )
323 char *name; 199 g_users[base].last_private = ul_now();
324 200}
325 /* first round? reset pointers! */
326 if (!state)
327 {
328 tmp = nicks;
329 len = strlen (text);
330 }
331 201
332 /* walk list .. */ 202void ul_public_action(char *name) {
333 while (tmp) 203 /* Ensure user and put him on the channel */
334 { 204 int base = ul_add(name, 1);
335 /* we found a match? */ 205 if( base >= 0 )
336 if (checkfn(tmp,text,len,value)) 206 g_users[base].last_public = ul_now();
337 {
338 /* copy nick, advance pointer for next call, return nick */
339 name = tmp->nick;
340 tmp = tmp->next;
341 return name;
342 }
343 else
344 {
345 tmp = tmp->next;
346 }
347 }
348 return NULL;
349} 207}
350 208
351/* nick completion functions for readline in vchat-ui.c */ 209/* Finding users ul_finduser? */
352char * 210char * ul_match_user(char *regex) {
353ul_nickcomp (const char *text, int state) 211 char *dest = tmpstr;
354{ 212 int i;
355 int ncasemode = 1; 213 regex_t preg;
356 char *name = NULL; 214
357 if (!state) ncasemode = 0; 215 *dest = 0;
358 if (!ncasemode) { 216 if( !regcomp( &preg, regex, REG_ICASE | REG_EXTENDED | REG_NEWLINE)) {
359 name = ulnc_complete(text,state,0,ulnc_casenick); 217
360 if (!state && !name) ncasemode = 1; 218 /* does the username match? */
219 /* XXX overflow for too many matches */
220 for( i=0; i<g_users_count; ++i )
221 if( !regexec( &preg, g_users[i].nick, 0, NULL, 0)) /* append username to list */
222 dest += snprintf ( dest, 256, " %s", g_users[i].nick);
223
361 } 224 }
362 if (ncasemode) 225 regfree( &preg );
363 name = ulnc_complete(text,state,0,ulnc_ncasenick); 226 return tmpstr;
364 if (name)
365 return strdup(name);
366 else
367 return NULL;
368} 227}
369 228
370int ulnc_casenickc(user *tmp, const char *text, int len, int value) { 229static int ul_compare_private( const void *a, const void *b ) {
371 return (!strncmp(tmp->nick, text, len) && (tmp->chan_valid) && (tmp->chan == value)); 230 const user *_a = (const user *)a, *_b = (const user *)b;
231 if( _a->last_private > _b->last_private ) return -1;
232 return 1;
372} 233}
373 234
374int ulnc_ncasenickc(user *tmp, const char *text, int len, int value) { 235static int ul_compare_begin_of_line_ncase( const void *a, const void *b ) {
375 return (!strncasecmp(tmp->nick, text, len) && (tmp->chan_valid) && (tmp->chan == value)); 236 const user *_a = (const user *)a, *_b = (const user *)b;
376} 237 size_t tmpstr_len;
238 int a_i, b_i;
377 239
378/* nick completion for channel, used by vchat-ui.c */ 240 /* First ensure that users in current channel win */
379char * 241 if( !(_a->flags & UL_IN_MY_CHAN ) ) return 1;
380ul_cnickcomp (const char *text, int state) 242 if( !(_b->flags & UL_IN_MY_CHAN ) ) return -1;
381{
382 int ncasemode = 1;
383 static char *name = NULL;
384 243
385 if (!state) ncasemode = 0; 244 tmpstr_len = strlen( tmpstr );
386 if (!ncasemode) { 245 a_i = strncasecmp( _a->nick, tmpstr, tmpstr_len );
387 name = ulnc_complete(text,state,chan,ulnc_casenickc); 246 b_i = strncasecmp( _b->nick, tmpstr, tmpstr_len );
388 if (!state && !name) ncasemode = 1; 247
389 } 248 if( a_i && b_i ) return 0; // Both nicks dont match
390 if (ncasemode) 249 if( !a_i && b_i ) return -1; // a matches insensitive, b doesnt
391 name = ulnc_complete(text,state,chan,ulnc_ncasenickc); 250 if( a_i && !b_i ) return 1; // b matches insensitive, a doesnt
392 if (name) { 251
393 snprintf(tmpstr,TMPSTRSIZE,"%s:",name); 252 /* From here both nicks match the prefix, ensure that own_nick
394 return strdup(tmpstr); 253 always appears last */
395 } else 254 if( _a->flags & UL_ME ) return 1;
396 return NULL; 255 if( _b->flags & UL_ME ) return -1;
256
257 /* Now the user with the most recent public activity wins */
258 if( _a->last_public > _b->last_public ) return -1;
259
260 return 1;
397} 261}
398 262
399int ulnc_casenickm(user *tmp, const char *text, int len, int value) { 263static int ul_compare_begin_of_line_case( const void *a, const void *b ) {
400 return (!strncmp(tmp->nick, text, len) && (tmp->messaged)); 264 const user *_a = (const user *)a, *_b = (const user *)b;
265 size_t tmpstr_len;
266 int a_i, b_i, a_s, b_s;
267
268 /* First ensure that users in current channel win */
269 if( !(_a->flags & UL_IN_MY_CHAN ) ) return 1;
270 if( !(_b->flags & UL_IN_MY_CHAN ) ) return -1;
271
272 tmpstr_len = strlen( tmpstr );
273 a_i = strncasecmp( _a->nick, tmpstr, tmpstr_len );
274 a_s = strncmp ( _a->nick, tmpstr, tmpstr_len );
275 b_i = strncasecmp( _b->nick, tmpstr, tmpstr_len );
276 b_s = strncmp ( _b->nick, tmpstr, tmpstr_len );
277
278 if( a_i && b_i ) return 0; // Both nicks dont match at all
279 if( !a_i && b_i ) return -1; // a matches insensitive, b doesnt
280 if( a_i && !b_i ) return 1; // b matches insensitive, a doesnt
281
282 if( !a_s && b_s ) return -1; // a matches sensitive, b doesnt
283 if( a_s && !b_s ) return 1; // b matches sensitive, a doesnt
284
285 /* From now we know that both match with same quality, ensure
286 that own nick always appears last */
287 if( _a->flags & UL_ME ) return 1;
288 if( _b->flags & UL_ME ) return -1;
289
290 /* Now the user with the most recent public activity wins */
291 if( _a->last_public > _b->last_public ) return -1;
292
293 return 1;
401} 294}
402 295
403int ulnc_ncasenickm(user *tmp, const char *text, int len, int value) { 296static int ul_compare_middle( const void *a, const void *b ) {
404 return (!strncasecmp(tmp->nick, text, len) && (tmp->messaged)); 297 const user *_a = (const user *)a, *_b = (const user *)b;
298 return strcasecmp( _b->nick, _a->nick );
405} 299}
406 300
407/* nick completion for channel, used by vchat-ui.c */ 301/* Nick completion function for readline */
408char * 302char **ul_complete_user(char *text, int start, int end ) {
409ul_mnickcomp (const char *text, int state) 303 char **result = 0;
410{ 304 int i, result_count = 0;
411 int ncasemode = 1; 305
412 static char *name = NULL; 306 /* Never want readline to complete filenames */
307 rl_attempted_completion_over = 1;
308
309 /* Prepare return array ... of max g_users_count (char*)
310 Plus least common prefix in [0] and null terminator
311 */
312 result = malloc( sizeof(char*) * ( 2 + g_users_count ) );
313 if( !result ) return 0;
314
315 if( start == 0 && end == 0 ) {
316 /* Completion on begin of line yields list of everyone we
317 were in private conversation, sorted by time of last .m */
318 qsort( g_users, g_users_count, sizeof(user), ul_compare_private );
319 for( i=0; i<g_users_count; ++i )
320 if( g_users[i].last_private ) {
321 snprintf( tmpstr, TMPSTRSIZE, ".m %s", g_users[i].nick );
322 result[++result_count] = strdup(tmpstr);
323 }
324 /* No common prefix */
325 if( result_count ) result[0] = strdup("");
326
327 } else if( start == 0 && end > 0 ) {
328 /* Completion on begin of line with some chars already typed yields
329 a list of everyone in channel, matching prefix, sorted by last
330 public activity */
331 snprintf( tmpstr, end + 1, "%s", text );
332 if( ul_case_first )
333 qsort( g_users, g_users_count, sizeof(user), ul_compare_begin_of_line_case );
334 else
335 qsort( g_users, g_users_count, sizeof(user), ul_compare_begin_of_line_ncase );
413 336
414 if (!state) ncasemode = 0; 337 for( i=0; i<g_users_count; ++i )
415 if (!ncasemode) { 338 if( ( g_users[i].flags & UL_IN_MY_CHAN ) && !strncasecmp( g_users[i].nick, tmpstr, end ) ) {
416 name = ulnc_complete(text,state,chan,ulnc_casenickm); 339 snprintf( tmpstr, TMPSTRSIZE, "%s:", g_users[i].nick );
417 if (!state && !name) ncasemode = 1; 340 result[++result_count] = strdup(tmpstr);
418 } 341 }
419 if (ncasemode) 342 /* Copy common prefix */
420 name = ulnc_complete(text,state,chan,ulnc_ncasenickm); 343 if( result_count ) result[0] = strndup(text, end);
421 if (name) { 344 } else if( start != end ) {
422 snprintf(tmpstr,TMPSTRSIZE,".m %s",name); 345 /* Completion in the middle of the line most likely is a .m XY<TAB>
423 return strdup(tmpstr); 346 and thus should complete all users, sorted alphabetically without
347 preferences. */
348 snprintf( tmpstr, end - start + 1, "%s", text );
349 qsort( g_users, g_users_count, sizeof(user), ul_compare_middle );
350 for( i=0; i<g_users_count; ++i )
351 if( !strncasecmp( g_users[i].nick, tmpstr, end - start ) )
352 result[++result_count] = strdup(g_users[i].nick);
353 /* Copy common prefix */
354 if( result_count ) result[0] = strndup(text, end - start);
355 } /* else: completion of an empty word in the middle yields nothing */
356
357 if( !result_count ) {
358 free( result );
359 result = 0;
424 } else 360 } else
425 return NULL; 361 result[++result_count] = 0;
362
363 return result;
426} 364}