diff options
author | erdgeist <> | 2012-02-27 00:06:17 +0000 |
---|---|---|
committer | erdgeist <> | 2012-02-27 00:06:17 +0000 |
commit | 7dbafe3f0fa465949ef66d800a8cbd0b191c9519 (patch) | |
tree | 45ad89dfee0154b76d2473a3d71ffbb0222bf7b4 /vchat-user.c | |
parent | f434f9cd4eabfcad3a90711494febbfd89e4ed5f (diff) |
Complete rewrite of user handling. HEADS UP\!
Diffstat (limited to 'vchat-user.c')
-rwxr-xr-x | vchat-user.c | 660 |
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 | ||
27 | struct 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 */ |
38 | char *vchat_us_version = "$Id$"; | 18 | char *vchat_us_version = "$Id$"; |
39 | 19 | ||
40 | /* externally used variables */ | 20 | typedef struct |
41 | /* current nick */ | ||
42 | char *nick = NULL; | ||
43 | /* current channel */ | ||
44 | int chan = 0; | ||
45 | /* userlist */ | ||
46 | user *nicks = NULL; | ||
47 | |||
48 | /* add user to userlist */ | ||
49 | void | ||
50 | ul_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 */ | ||
96 | void | ||
97 | ul_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 */ | 27 | static user *g_users; //< all users, incl self |
105 | if (!nicks) | 28 | static size_t g_users_count; //< number of users in list |
106 | return; | 29 | static char *g_nick; //< own nick |
107 | /* the user on top of list? */ | 30 | static int g_channel; //< own channel |
108 | if (!strcmp (name, nicks->nick)) | 31 | int ul_case_first = 0; |
109 | { | 32 | |
110 | /* remove user and copy next in list */ | 33 | static 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 */ | 44 | static int64_t ul_now() { |
139 | void | 45 | struct timeval now; |
140 | ul_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 | ||
153 | user * | 50 | /* own nick and channel setters/getters */ |
154 | ul_finduser (char *name) { | 51 | void 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 | ||
172 | char * | 65 | setstroption(CF_NICK, nick); |
173 | ul_matchuser( char *regex) { | 66 | } |
174 | user *tmp = nicks; | ||
175 | char *dest = tmpstr; | ||
176 | regex_t preg; | ||
177 | 67 | ||
178 | *dest = 0; | 68 | void 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 | ||
191 | static void | 79 | char const *own_nick_get( ) { |
192 | ul_usertofront( user *who ) { | 80 | return g_nick; |
193 | user *tmp = nicks; | 81 | } |
194 | 82 | ||
195 | while( tmp->next && ( tmp->next != who ) ) | 83 | int 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 ) { | 88 | int 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 | ||
208 | void | 92 | /* Add/remove/rename */ |
209 | ul_msgto (char *name) { | 93 | int 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 | ||
218 | void | 123 | int ul_del(char *name) { |
219 | ul_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 */ |
229 | void | 133 | memmove( g_users + base, g_users + base + 1, ( g_users_count - base - 1 ) * sizeof( user ) ); |
230 | ul_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 */ | 140 | int ul_rename(char *oldname, char *newname) { |
244 | void | 141 | /* Ensure user */ |
245 | ul_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) | 154 | void 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 */ | 162 | void ul_rebuild_list( ) { |
267 | void | 163 | int i; |
268 | ul_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 */ | 168 | void ul_clean() { |
290 | void | 169 | int i; |
291 | ul_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 | ||
311 | int ulnc_casenick(user *tmp, const char *text, int len, int value) { | 178 | /* Seting state */ |
312 | return (!strncmp(tmp->nick, text, len)); | 179 | void ul_leave_chan(char *name) { |
180 | /* Ensure user and kick him off the channel */ | ||
181 | ul_add(name, 0); | ||
313 | } | 182 | } |
314 | 183 | ||
315 | int ulnc_ncasenick(user *tmp, const char *text, int len, int value) { | 184 | void 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 | ||
319 | char * | 195 | void ul_private_action(char *name) { |
320 | ulnc_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 .. */ | 202 | void 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? */ |
352 | char * | 210 | char * ul_match_user(char *regex) { |
353 | ul_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 | ||
370 | int ulnc_casenickc(user *tmp, const char *text, int len, int value) { | 229 | static 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 | ||
374 | int ulnc_ncasenickc(user *tmp, const char *text, int len, int value) { | 235 | static 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 */ |
379 | char * | 241 | if( !(_a->flags & UL_IN_MY_CHAN ) ) return 1; |
380 | ul_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 | ||
399 | int ulnc_casenickm(user *tmp, const char *text, int len, int value) { | 263 | static 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 | ||
403 | int ulnc_ncasenickm(user *tmp, const char *text, int len, int value) { | 296 | static 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 */ |
408 | char * | 302 | char **ul_complete_user(char *text, int start, int end ) { |
409 | ul_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 | } |