comparison mcabber/mcabber/hbuf.c @ 1668:41c26b7d2890

Install mcabber headers * Change mcabber headers naming scheme * Move 'src/' -> 'mcabber/' * Add missing include <mcabber/config.h>'s * Create and install clean config.h version in 'include/' * Move "dirty" config.h version to 'mcabber/' * Add $(top_srcdir) to compiler include path * Update modules HOWTO
author Myhailo Danylenko <isbear@ukrpost.net>
date Mon, 18 Jan 2010 15:36:19 +0200
parents mcabber/src/hbuf.c@f4a2c6f767d1
children e6e89b1d7831
comparison
equal deleted inserted replaced
1667:8af0e0ad20ad 1668:41c26b7d2890
1 /*
2 * hbuf.c -- History buffer implementation
3 *
4 * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or (at
9 * your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19 * USA
20 */
21
22 #include <string.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <unistd.h>
26
27 #include "hbuf.h"
28 #include "utils.h"
29 #include "utf8.h"
30 #include "screen.h"
31
32
33 /* This is a private structure type */
34
35 typedef struct {
36 char *ptr;
37 char *ptr_end; // beginning of the block
38 char *ptr_end_alloc; // end of the current persistent block
39 guchar flags;
40
41 // XXX This should certainly be a pointer, and be allocated only when needed
42 // (for ex. when HBB_FLAG_PERSISTENT is set).
43 struct { // hbuf_line_info
44 time_t timestamp;
45 unsigned mucnicklen;
46 guint flags;
47 gpointer xep184;
48 } prefix;
49 } hbuf_block;
50
51
52 // do_wrap(p_hbuf, first_hbuf_elt, width)
53 // Wrap hbuf lines with the specified width.
54 // '\n' are handled by this routine (they are removed and persistent lines
55 // are created).
56 // All hbuf elements are processed, starting from first_hbuf_elt.
57 static inline void do_wrap(GList **p_hbuf, GList *first_hbuf_elt,
58 unsigned int width)
59 {
60 GList *curr_elt = first_hbuf_elt;
61
62 // Let's add non-persistent blocs if necessary
63 // - If there are '\n' in the string
64 // - If length > width (and width != 0)
65 while (curr_elt) {
66 hbuf_block *hbuf_b_curr, *hbuf_b_prev;
67 char *c, *end;
68 char *br = NULL; // break pointer
69 char *cr = NULL; // CR pointer
70 unsigned int cur_w = 0;
71
72 // We want to break where we can find a space char or a CR
73
74 hbuf_b_curr = (hbuf_block*)(curr_elt->data);
75 hbuf_b_prev = hbuf_b_curr;
76 c = hbuf_b_curr->ptr;
77
78 while (*c && (!width || cur_w <= width)) {
79 if (*c == '\n') {
80 br = cr = c;
81 *c = 0;
82 break;
83 }
84 if (iswblank(get_char(c)))
85 br = c;
86 cur_w += get_char_width(c);
87 c = next_char(c);
88 }
89
90 if (cr || (*c && cur_w > width)) {
91 if (!br || br == hbuf_b_curr->ptr)
92 br = c;
93 else
94 br = next_char(br);
95 end = hbuf_b_curr->ptr_end;
96 hbuf_b_curr->ptr_end = br;
97 // Create another block
98 hbuf_b_curr = g_new0(hbuf_block, 1);
99 // The block must be persistent after a CR
100 if (cr) {
101 hbuf_b_curr->ptr = hbuf_b_prev->ptr_end + 1; // == cr+1
102 hbuf_b_curr->flags = HBB_FLAG_PERSISTENT;
103 } else {
104 hbuf_b_curr->ptr = hbuf_b_prev->ptr_end; // == br
105 hbuf_b_curr->flags = 0;
106 }
107 hbuf_b_curr->ptr_end = end;
108 hbuf_b_curr->ptr_end_alloc = hbuf_b_prev->ptr_end_alloc;
109 // This is OK because insert_before(NULL) == append():
110 *p_hbuf = g_list_insert_before(*p_hbuf, curr_elt->next, hbuf_b_curr);
111 }
112 curr_elt = g_list_next(curr_elt);
113 }
114 }
115
116 // hbuf_add_line(p_hbuf, text, prefix_flags, width, maxhbufblocks)
117 // Add a line to the given buffer. If width is not null, then lines are
118 // wrapped at this length.
119 // maxhbufblocks is the maximum number of hbuf blocks we can allocate. If
120 // null, there is no limit. If non-null, it should be >= 2.
121 //
122 // Note 1: Splitting according to width won't work if there are tabs; they
123 // should be expanded before.
124 // Note 2: width does not include the ending \0.
125 void hbuf_add_line(GList **p_hbuf, const char *text, time_t timestamp,
126 guint prefix_flags, guint width, guint maxhbufblocks,
127 unsigned mucnicklen, gpointer xep184)
128 {
129 GList *curr_elt;
130 char *line;
131 guint hbb_blocksize, textlen;
132 hbuf_block *hbuf_block_elt;
133
134 if (!text) return;
135
136 prefix_flags |= (xep184 ? HBB_PREFIX_RECEIPT : 0);
137
138 textlen = strlen(text);
139 hbb_blocksize = MAX(textlen+1, HBB_BLOCKSIZE);
140
141 hbuf_block_elt = g_new0(hbuf_block, 1);
142 hbuf_block_elt->prefix.timestamp = timestamp;
143 hbuf_block_elt->prefix.flags = prefix_flags;
144 hbuf_block_elt->prefix.mucnicklen = mucnicklen;
145 hbuf_block_elt->prefix.xep184 = xep184;
146 if (!*p_hbuf) {
147 hbuf_block_elt->ptr = g_new(char, hbb_blocksize);
148 if (!hbuf_block_elt->ptr) {
149 g_free(hbuf_block_elt);
150 return;
151 }
152 hbuf_block_elt->flags = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT;
153 hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize;
154 } else {
155 hbuf_block *hbuf_b_prev;
156 // Set p_hbuf to the end of the list, to speed up history loading
157 // (or CPU time will be used by g_list_last() for each line)
158 *p_hbuf = g_list_last(*p_hbuf);
159 hbuf_b_prev = (*p_hbuf)->data;
160 hbuf_block_elt->ptr = hbuf_b_prev->ptr_end;
161 hbuf_block_elt->flags = HBB_FLAG_PERSISTENT;
162 hbuf_block_elt->ptr_end_alloc = hbuf_b_prev->ptr_end_alloc;
163 }
164 *p_hbuf = g_list_append(*p_hbuf, hbuf_block_elt);
165
166 if (hbuf_block_elt->ptr + textlen >= hbuf_block_elt->ptr_end_alloc) {
167 // Too long for the current allocated bloc, we need another one
168 if (!maxhbufblocks || textlen >= HBB_BLOCKSIZE) {
169 // No limit, let's allocate a new block
170 // If the message text is big, we won't bother to reuse an old block
171 // as well (it could be too small and cause a segfault).
172 hbuf_block_elt->ptr = g_new0(char, hbb_blocksize);
173 hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize;
174 // XXX We should check the return value.
175 } else {
176 GList *hbuf_head, *hbuf_elt;
177 hbuf_block *hbuf_b_elt;
178 guint n = 0;
179 hbuf_head = g_list_first(*p_hbuf);
180 // We need at least 2 allocated blocks
181 if (maxhbufblocks == 1)
182 maxhbufblocks = 2;
183 // Let's count the number of allocated areas
184 for (hbuf_elt = hbuf_head; hbuf_elt; hbuf_elt = g_list_next(hbuf_elt)) {
185 hbuf_b_elt = (hbuf_block*)(hbuf_elt->data);
186 if (hbuf_b_elt->flags & HBB_FLAG_ALLOC)
187 n++;
188 }
189 // If we can't allocate a new area, reuse the previous block(s)
190 if (n < maxhbufblocks) {
191 hbuf_block_elt->ptr = g_new0(char, hbb_blocksize);
192 hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize;
193 } else {
194 // Let's use an old block, and free the extra blocks if needed
195 char *allocated_block = NULL;
196 char *end_of_allocated_block = NULL;
197 while (n >= maxhbufblocks) {
198 int start_of_block = 1;
199 for (hbuf_elt = hbuf_head; hbuf_elt; hbuf_elt = hbuf_head) {
200 hbuf_b_elt = (hbuf_block*)(hbuf_elt->data);
201 if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) {
202 if (start_of_block-- == 0)
203 break;
204 if (n == maxhbufblocks) {
205 allocated_block = hbuf_b_elt->ptr;
206 end_of_allocated_block = hbuf_b_elt->ptr_end_alloc;
207 } else {
208 g_free(hbuf_b_elt->ptr);
209 }
210 }
211 g_free(hbuf_b_elt);
212 hbuf_head = *p_hbuf = g_list_delete_link(hbuf_head, hbuf_elt);
213 }
214 n--;
215 }
216 memset(allocated_block, 0, end_of_allocated_block-allocated_block);
217 hbuf_block_elt->ptr = allocated_block;
218 hbuf_block_elt->ptr_end_alloc = end_of_allocated_block;
219 }
220 }
221 hbuf_block_elt->flags = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT;
222 }
223
224 line = hbuf_block_elt->ptr;
225 // Ok, now we can copy the text..
226 strcpy(line, text);
227 hbuf_block_elt->ptr_end = line + textlen + 1;
228
229 curr_elt = g_list_last(*p_hbuf);
230
231 // Wrap lines and handle CRs ('\n')
232 do_wrap(p_hbuf, curr_elt, width);
233 }
234
235 // hbuf_free()
236 // Destroys all hbuf list.
237 void hbuf_free(GList **p_hbuf)
238 {
239 hbuf_block *hbuf_b_elt;
240 GList *hbuf_elt;
241 GList *first_elt = g_list_first(*p_hbuf);
242
243 for (hbuf_elt = first_elt; hbuf_elt; hbuf_elt = g_list_next(hbuf_elt)) {
244 hbuf_b_elt = (hbuf_block*)(hbuf_elt->data);
245 if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) {
246 g_free(hbuf_b_elt->ptr);
247 }
248 g_free(hbuf_b_elt);
249 }
250
251 g_list_free(first_elt);
252 *p_hbuf = NULL;
253 }
254
255 // hbuf_rebuild()
256 // Rebuild all hbuf list, with the new width.
257 // If width == 0, lines are not wrapped.
258 void hbuf_rebuild(GList **p_hbuf, unsigned int width)
259 {
260 GList *first_elt, *curr_elt, *next_elt;
261 hbuf_block *hbuf_b_curr, *hbuf_b_next;
262
263 // *p_hbuf needs to be the head of the list
264 first_elt = *p_hbuf = g_list_first(*p_hbuf);
265
266 // #1 Remove non-persistent blocks (ptr_end should be updated!)
267 curr_elt = first_elt;
268 while (curr_elt) {
269 next_elt = g_list_next(curr_elt);
270 // Last element?
271 if (!next_elt)
272 break;
273 hbuf_b_curr = (hbuf_block*)(curr_elt->data);
274 hbuf_b_next = (hbuf_block*)(next_elt->data);
275 // Is next line not-persistent?
276 if (!(hbuf_b_next->flags & HBB_FLAG_PERSISTENT)) {
277 hbuf_b_curr->ptr_end = hbuf_b_next->ptr_end;
278 g_free(hbuf_b_next);
279 curr_elt = g_list_delete_link(curr_elt, next_elt);
280 } else
281 curr_elt = next_elt;
282 }
283 // #2 Go back to head and create non-persistent blocks when needed
284 if (width)
285 do_wrap(p_hbuf, first_elt, width);
286 }
287
288 // hbuf_previous_persistent()
289 // Returns the previous persistent block (line). If the given line is
290 // persistent, then it is returned.
291 // This function is used for example when resizing a buffer. If the top of the
292 // screen is on a non-persistent block, then a screen resize could destroy this
293 // line...
294 GList *hbuf_previous_persistent(GList *l_line)
295 {
296 hbuf_block *hbuf_b_elt;
297
298 while (l_line) {
299 hbuf_b_elt = (hbuf_block*)l_line->data;
300 if (hbuf_b_elt->flags & HBB_FLAG_PERSISTENT)
301 return l_line;
302 l_line = g_list_previous(l_line);
303 }
304
305 return NULL;
306 }
307
308 // hbuf_get_lines(hbuf, n)
309 // Returns an array of n hbb_line pointers
310 // (The first line will be the line currently pointed by hbuf)
311 // Note: The caller should free the array, the hbb_line pointers and the
312 // text pointers after use.
313 hbb_line **hbuf_get_lines(GList *hbuf, unsigned int n)
314 {
315 unsigned int i;
316 hbuf_block *blk;
317 guint last_persist_prefixflags = 0;
318 GList *last_persist; // last persistent flags
319 hbb_line **array, **array_elt;
320
321 // To be able to correctly highlight multi-line messages,
322 // we need to look at the last non-null prefix, which should be the first
323 // line of the message.
324 last_persist = hbuf_previous_persistent(hbuf);
325 while (last_persist) {
326 blk = (hbuf_block*)last_persist->data;
327 if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) {
328 last_persist_prefixflags = blk->prefix.flags;
329 break;
330 }
331 last_persist = g_list_previous(last_persist);
332 }
333
334 array = g_new0(hbb_line*, n);
335 array_elt = array;
336
337 for (i = 0 ; i < n ; i++) {
338 if (hbuf) {
339 int maxlen;
340
341 blk = (hbuf_block*)(hbuf->data);
342 maxlen = blk->ptr_end - blk->ptr;
343 *array_elt = (hbb_line*)g_new(hbb_line, 1);
344 (*array_elt)->timestamp = blk->prefix.timestamp;
345 (*array_elt)->flags = blk->prefix.flags;
346 (*array_elt)->mucnicklen = blk->prefix.mucnicklen;
347 (*array_elt)->text = g_strndup(blk->ptr, maxlen);
348
349 if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) {
350 last_persist_prefixflags = blk->prefix.flags;
351 } else {
352 // Propagate highlighting flags
353 (*array_elt)->flags |= last_persist_prefixflags &
354 (HBB_PREFIX_HLIGHT_OUT | HBB_PREFIX_HLIGHT |
355 HBB_PREFIX_INFO | HBB_PREFIX_IN);
356 // Continuation of a message - omit the prefix
357 (*array_elt)->flags |= HBB_PREFIX_CONT;
358 (*array_elt)->mucnicklen = 0; // The nick is in the first one
359 }
360
361 hbuf = g_list_next(hbuf);
362 } else
363 break;
364
365 array_elt++;
366 }
367
368 return array;
369 }
370
371 // hbuf_search(hbuf, direction, string)
372 // Look backward/forward for a line containing string in the history buffer
373 // Search starts at hbuf, and goes forward if direction == 1, backward if -1
374 GList *hbuf_search(GList *hbuf, int direction, const char *string)
375 {
376 hbuf_block *blk;
377
378 for (;;) {
379 if (direction > 0)
380 hbuf = g_list_next(hbuf);
381 else
382 hbuf = g_list_previous(hbuf);
383
384 if (!hbuf) break;
385
386 blk = (hbuf_block*)(hbuf->data);
387 // XXX blk->ptr is (maybe) not really correct, because the match should
388 // not be after ptr_end. We should check that...
389 if (strcasestr(blk->ptr, string))
390 break;
391 }
392
393 return hbuf;
394 }
395
396 // hbuf_jump_date(hbuf, t)
397 // Return a pointer to the first line after date t in the history buffer
398 GList *hbuf_jump_date(GList *hbuf, time_t t)
399 {
400 hbuf_block *blk;
401
402 hbuf = g_list_first(hbuf);
403
404 for ( ; hbuf && g_list_next(hbuf); hbuf = g_list_next(hbuf)) {
405 blk = (hbuf_block*)(hbuf->data);
406 if (blk->prefix.timestamp >= t) break;
407 }
408
409 return hbuf;
410 }
411
412 // hbuf_jump_percent(hbuf, pc)
413 // Return a pointer to the line at % pc of the history buffer
414 GList *hbuf_jump_percent(GList *hbuf, int pc)
415 {
416 guint hlen;
417
418 hbuf = g_list_first(hbuf);
419 hlen = g_list_length(hbuf);
420
421 return g_list_nth(hbuf, pc*hlen/100);
422 }
423
424 // hbuf_dump_to_file(hbuf, filename)
425 // Save the buffer to a file.
426 void hbuf_dump_to_file(GList *hbuf, const char *filename)
427 {
428 hbuf_block *blk;
429 hbb_line line;
430 guint last_persist_prefixflags = 0;
431 guint prefixwidth;
432 char pref[96];
433 FILE *fp;
434 struct stat statbuf;
435
436 if (!stat(filename, &statbuf)) {
437 scr_LogPrint(LPRINT_NORMAL, "The file already exists.");
438 return;
439 }
440 fp = fopen(filename, "w");
441 if (!fp) {
442 scr_LogPrint(LPRINT_NORMAL, "Unable to open the file.");
443 return;
444 }
445
446 prefixwidth = scr_getprefixwidth();
447 prefixwidth = MIN(prefixwidth, sizeof pref);
448
449 for (hbuf = g_list_first(hbuf); hbuf; hbuf = g_list_next(hbuf)) {
450 int maxlen;
451
452 blk = (hbuf_block*)(hbuf->data);
453 maxlen = blk->ptr_end - blk->ptr;
454
455 memset(&line, 0, sizeof(line));
456 line.timestamp = blk->prefix.timestamp;
457 line.flags = blk->prefix.flags;
458 line.mucnicklen = blk->prefix.mucnicklen;
459 line.text = g_strndup(blk->ptr, maxlen);
460
461 if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) {
462 last_persist_prefixflags = blk->prefix.flags;
463 } else {
464 // Propagate highlighting flags
465 line.flags |= last_persist_prefixflags &
466 (HBB_PREFIX_HLIGHT_OUT | HBB_PREFIX_HLIGHT |
467 HBB_PREFIX_INFO | HBB_PREFIX_IN);
468 // Continuation of a message - omit the prefix
469 line.flags |= HBB_PREFIX_CONT;
470 line.mucnicklen = 0; // The nick is in the first one
471 }
472
473 scr_line_prefix(&line, pref, prefixwidth);
474 fprintf(fp, "%s%s\n", pref, line.text);
475 }
476
477 fclose(fp);
478 return;
479 }
480
481 // hbuf_remove_receipt(hbuf, xep184)
482 // Remove the Receipt Flag for the message with the given xep184 id
483 // Returns TRUE if it was found and removed, otherwise FALSE
484 gboolean hbuf_remove_receipt(GList *hbuf, gpointer xep184)
485 {
486 hbuf_block *blk;
487
488 hbuf = g_list_first(hbuf);
489
490 for ( ; hbuf; hbuf = g_list_next(hbuf)) {
491 blk = (hbuf_block*)(hbuf->data);
492 if (blk->prefix.xep184 == xep184) {
493 blk->prefix.xep184 = NULL;
494 blk->prefix.flags ^= HBB_PREFIX_RECEIPT;
495 return TRUE;
496 }
497 }
498 return FALSE;
499 }
500
501 // hbuf_get_blocks_number()
502 // Returns the number of allocated hbuf_block's.
503 guint hbuf_get_blocks_number(GList *hbuf)
504 {
505 hbuf_block *hbuf_b_elt;
506 guint count = 0U;
507
508 for (hbuf = g_list_first(hbuf); hbuf; hbuf = g_list_next(hbuf)) {
509 hbuf_b_elt = (hbuf_block*)(hbuf->data);
510 if (hbuf_b_elt->flags & HBB_FLAG_ALLOC)
511 count++;
512 }
513 return count;
514 }
515
516 /* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */