diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/hbuf.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,516 @@
+/*
+ * hbuf.c       -- History buffer implementation
+ *
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "hbuf.h"
+#include "utils.h"
+#include "utf8.h"
+#include "screen.h"
+
+
+/* This is a private structure type */
+
+typedef struct {
+  char *ptr;
+  char *ptr_end;        // beginning of the block
+  char *ptr_end_alloc;  // end of the current persistent block
+  guchar flags;
+
+  // XXX This should certainly be a pointer, and be allocated only when needed
+  // (for ex. when HBB_FLAG_PERSISTENT is set).
+  struct { // hbuf_line_info
+    time_t timestamp;
+    unsigned mucnicklen;
+    guint  flags;
+    gpointer xep184;
+  } prefix;
+} hbuf_block;
+
+
+//  do_wrap(p_hbuf, first_hbuf_elt, width)
+// Wrap hbuf lines with the specified width.
+// '\n' are handled by this routine (they are removed and persistent lines
+// are created).
+// All hbuf elements are processed, starting from first_hbuf_elt.
+static inline void do_wrap(GList **p_hbuf, GList *first_hbuf_elt,
+                           unsigned int width)
+{
+  GList *curr_elt = first_hbuf_elt;
+
+  // Let's add non-persistent blocs if necessary
+  // - If there are '\n' in the string
+  // - If length > width (and width != 0)
+  while (curr_elt) {
+    hbuf_block *hbuf_b_curr, *hbuf_b_prev;
+    char *c, *end;
+    char *br = NULL; // break pointer
+    char *cr = NULL; // CR pointer
+    unsigned int cur_w = 0;
+
+    // We want to break where we can find a space char or a CR
+
+    hbuf_b_curr = (hbuf_block*)(curr_elt->data);
+    hbuf_b_prev = hbuf_b_curr;
+    c = hbuf_b_curr->ptr;
+
+    while (*c && (!width || cur_w <= width)) {
+      if (*c == '\n') {
+        br = cr = c;
+        *c = 0;
+        break;
+      }
+      if (iswblank(get_char(c)))
+        br = c;
+      cur_w += get_char_width(c);
+      c = next_char(c);
+    }
+
+    if (cr || (*c && cur_w > width)) {
+      if (!br || br == hbuf_b_curr->ptr)
+        br = c;
+      else
+        br = next_char(br);
+      end = hbuf_b_curr->ptr_end;
+      hbuf_b_curr->ptr_end = br;
+      // Create another block
+      hbuf_b_curr = g_new0(hbuf_block, 1);
+      // The block must be persistent after a CR
+      if (cr) {
+        hbuf_b_curr->ptr    = hbuf_b_prev->ptr_end + 1; // == cr+1
+        hbuf_b_curr->flags  = HBB_FLAG_PERSISTENT;
+      } else {
+        hbuf_b_curr->ptr    = hbuf_b_prev->ptr_end; // == br
+        hbuf_b_curr->flags    = 0;
+      }
+      hbuf_b_curr->ptr_end  = end;
+      hbuf_b_curr->ptr_end_alloc = hbuf_b_prev->ptr_end_alloc;
+      // This is OK because insert_before(NULL) == append():
+      *p_hbuf = g_list_insert_before(*p_hbuf, curr_elt->next, hbuf_b_curr);
+    }
+    curr_elt = g_list_next(curr_elt);
+  }
+}
+
+//  hbuf_add_line(p_hbuf, text, prefix_flags, width, maxhbufblocks)
+// Add a line to the given buffer.  If width is not null, then lines are
+// wrapped at this length.
+// maxhbufblocks is the maximum number of hbuf blocks we can allocate.  If
+// null, there is no limit.  If non-null, it should be >= 2.
+//
+// Note 1: Splitting according to width won't work if there are tabs; they
+//         should be expanded before.
+// Note 2: width does not include the ending \0.
+void hbuf_add_line(GList **p_hbuf, const char *text, time_t timestamp,
+        guint prefix_flags, guint width, guint maxhbufblocks,
+        unsigned mucnicklen, gpointer xep184)
+{
+  GList *curr_elt;
+  char *line;
+  guint hbb_blocksize, textlen;
+  hbuf_block *hbuf_block_elt;
+
+  if (!text) return;
+
+  prefix_flags |= (xep184 ? HBB_PREFIX_RECEIPT : 0);
+
+  textlen = strlen(text);
+  hbb_blocksize = MAX(textlen+1, HBB_BLOCKSIZE);
+
+  hbuf_block_elt = g_new0(hbuf_block, 1);
+  hbuf_block_elt->prefix.timestamp  = timestamp;
+  hbuf_block_elt->prefix.flags      = prefix_flags;
+  hbuf_block_elt->prefix.mucnicklen = mucnicklen;
+  hbuf_block_elt->prefix.xep184     = xep184;
+  if (!*p_hbuf) {
+    hbuf_block_elt->ptr  = g_new(char, hbb_blocksize);
+    if (!hbuf_block_elt->ptr) {
+      g_free(hbuf_block_elt);
+      return;
+    }
+    hbuf_block_elt->flags  = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT;
+    hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize;
+  } else {
+    hbuf_block *hbuf_b_prev;
+    // Set p_hbuf to the end of the list, to speed up history loading
+    // (or CPU time will be used by g_list_last() for each line)
+    *p_hbuf = g_list_last(*p_hbuf);
+    hbuf_b_prev = (*p_hbuf)->data;
+    hbuf_block_elt->ptr    = hbuf_b_prev->ptr_end;
+    hbuf_block_elt->flags  = HBB_FLAG_PERSISTENT;
+    hbuf_block_elt->ptr_end_alloc = hbuf_b_prev->ptr_end_alloc;
+  }
+  *p_hbuf = g_list_append(*p_hbuf, hbuf_block_elt);
+
+  if (hbuf_block_elt->ptr + textlen >= hbuf_block_elt->ptr_end_alloc) {
+    // Too long for the current allocated bloc, we need another one
+    if (!maxhbufblocks || textlen >= HBB_BLOCKSIZE) {
+      // No limit, let's allocate a new block
+      // If the message text is big, we won't bother to reuse an old block
+      // as well (it could be too small and cause a segfault).
+      hbuf_block_elt->ptr  = g_new0(char, hbb_blocksize);
+      hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize;
+      // XXX We should check the return value.
+    } else {
+      GList *hbuf_head, *hbuf_elt;
+      hbuf_block *hbuf_b_elt;
+      guint n = 0;
+      hbuf_head = g_list_first(*p_hbuf);
+      // We need at least 2 allocated blocks
+      if (maxhbufblocks == 1)
+        maxhbufblocks = 2;
+      // Let's count the number of allocated areas
+      for (hbuf_elt = hbuf_head; hbuf_elt; hbuf_elt = g_list_next(hbuf_elt)) {
+        hbuf_b_elt = (hbuf_block*)(hbuf_elt->data);
+        if (hbuf_b_elt->flags & HBB_FLAG_ALLOC)
+          n++;
+      }
+      // If we can't allocate a new area, reuse the previous block(s)
+      if (n < maxhbufblocks) {
+        hbuf_block_elt->ptr  = g_new0(char, hbb_blocksize);
+        hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize;
+      } else {
+        // Let's use an old block, and free the extra blocks if needed
+        char *allocated_block = NULL;
+        char *end_of_allocated_block = NULL;
+        while (n >= maxhbufblocks) {
+          int start_of_block = 1;
+          for (hbuf_elt = hbuf_head; hbuf_elt; hbuf_elt = hbuf_head) {
+            hbuf_b_elt = (hbuf_block*)(hbuf_elt->data);
+            if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) {
+              if (start_of_block-- == 0)
+                break;
+              if (n == maxhbufblocks) {
+                allocated_block = hbuf_b_elt->ptr;
+                end_of_allocated_block = hbuf_b_elt->ptr_end_alloc;
+              } else {
+                g_free(hbuf_b_elt->ptr);
+              }
+            }
+            g_free(hbuf_b_elt);
+            hbuf_head = *p_hbuf = g_list_delete_link(hbuf_head, hbuf_elt);
+          }
+          n--;
+        }
+        memset(allocated_block, 0, end_of_allocated_block-allocated_block);
+        hbuf_block_elt->ptr = allocated_block;
+        hbuf_block_elt->ptr_end_alloc = end_of_allocated_block;
+      }
+    }
+    hbuf_block_elt->flags  = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT;
+  }
+
+  line = hbuf_block_elt->ptr;
+  // Ok, now we can copy the text..
+  strcpy(line, text);
+  hbuf_block_elt->ptr_end = line + textlen + 1;
+
+  curr_elt = g_list_last(*p_hbuf);
+
+  // Wrap lines and handle CRs ('\n')
+  do_wrap(p_hbuf, curr_elt, width);
+}
+
+//  hbuf_free()
+// Destroys all hbuf list.
+void hbuf_free(GList **p_hbuf)
+{
+  hbuf_block *hbuf_b_elt;
+  GList *hbuf_elt;
+  GList *first_elt = g_list_first(*p_hbuf);
+
+  for (hbuf_elt = first_elt; hbuf_elt; hbuf_elt = g_list_next(hbuf_elt)) {
+    hbuf_b_elt = (hbuf_block*)(hbuf_elt->data);
+    if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) {
+      g_free(hbuf_b_elt->ptr);
+    }
+    g_free(hbuf_b_elt);
+  }
+
+  g_list_free(first_elt);
+  *p_hbuf = NULL;
+}
+
+//  hbuf_rebuild()
+// Rebuild all hbuf list, with the new width.
+// If width == 0, lines are not wrapped.
+void hbuf_rebuild(GList **p_hbuf, unsigned int width)
+{
+  GList *first_elt, *curr_elt, *next_elt;
+  hbuf_block *hbuf_b_curr, *hbuf_b_next;
+
+  // *p_hbuf needs to be the head of the list
+  first_elt = *p_hbuf = g_list_first(*p_hbuf);
+
+  // #1 Remove non-persistent blocks (ptr_end should be updated!)
+  curr_elt = first_elt;
+  while (curr_elt) {
+    next_elt = g_list_next(curr_elt);
+    // Last element?
+    if (!next_elt)
+      break;
+    hbuf_b_curr = (hbuf_block*)(curr_elt->data);
+    hbuf_b_next = (hbuf_block*)(next_elt->data);
+    // Is next line not-persistent?
+    if (!(hbuf_b_next->flags & HBB_FLAG_PERSISTENT)) {
+      hbuf_b_curr->ptr_end = hbuf_b_next->ptr_end;
+      g_free(hbuf_b_next);
+      curr_elt = g_list_delete_link(curr_elt, next_elt);
+    } else
+      curr_elt = next_elt;
+  }
+  // #2 Go back to head and create non-persistent blocks when needed
+  if (width)
+    do_wrap(p_hbuf, first_elt, width);
+}
+
+//  hbuf_previous_persistent()
+// Returns the previous persistent block (line).  If the given line is
+// persistent, then it is returned.
+// This function is used for example when resizing a buffer.  If the top of the
+// screen is on a non-persistent block, then a screen resize could destroy this
+// line...
+GList *hbuf_previous_persistent(GList *l_line)
+{
+  hbuf_block *hbuf_b_elt;
+
+  while (l_line) {
+    hbuf_b_elt = (hbuf_block*)l_line->data;
+    if (hbuf_b_elt->flags & HBB_FLAG_PERSISTENT)
+      return l_line;
+    l_line = g_list_previous(l_line);
+  }
+
+  return NULL;
+}
+
+//  hbuf_get_lines(hbuf, n)
+// Returns an array of n hbb_line pointers
+// (The first line will be the line currently pointed by hbuf)
+// Note: The caller should free the array, the hbb_line pointers and the
+// text pointers after use.
+hbb_line **hbuf_get_lines(GList *hbuf, unsigned int n)
+{
+  unsigned int i;
+  hbuf_block *blk;
+  guint last_persist_prefixflags = 0;
+  GList *last_persist;  // last persistent flags
+  hbb_line **array, **array_elt;
+
+  // To be able to correctly highlight multi-line messages,
+  // we need to look at the last non-null prefix, which should be the first
+  // line of the message.
+  last_persist = hbuf_previous_persistent(hbuf);
+  while (last_persist) {
+    blk = (hbuf_block*)last_persist->data;
+    if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) {
+      last_persist_prefixflags = blk->prefix.flags;
+      break;
+    }
+    last_persist = g_list_previous(last_persist);
+  }
+
+  array = g_new0(hbb_line*, n);
+  array_elt = array;
+
+  for (i = 0 ; i < n ; i++) {
+    if (hbuf) {
+      int maxlen;
+
+      blk = (hbuf_block*)(hbuf->data);
+      maxlen = blk->ptr_end - blk->ptr;
+      *array_elt = (hbb_line*)g_new(hbb_line, 1);
+      (*array_elt)->timestamp  = blk->prefix.timestamp;
+      (*array_elt)->flags      = blk->prefix.flags;
+      (*array_elt)->mucnicklen = blk->prefix.mucnicklen;
+      (*array_elt)->text       = g_strndup(blk->ptr, maxlen);
+
+      if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) {
+        last_persist_prefixflags = blk->prefix.flags;
+      } else {
+        // Propagate highlighting flags
+        (*array_elt)->flags |= last_persist_prefixflags &
+                               (HBB_PREFIX_HLIGHT_OUT | HBB_PREFIX_HLIGHT |
+                                HBB_PREFIX_INFO | HBB_PREFIX_IN);
+        // Continuation of a message - omit the prefix
+        (*array_elt)->flags |= HBB_PREFIX_CONT;
+        (*array_elt)->mucnicklen = 0; // The nick is in the first one
+      }
+
+      hbuf = g_list_next(hbuf);
+    } else
+      break;
+
+    array_elt++;
+  }
+
+  return array;
+}
+
+//  hbuf_search(hbuf, direction, string)
+// Look backward/forward for a line containing string in the history buffer
+// Search starts at hbuf, and goes forward if direction == 1, backward if -1
+GList *hbuf_search(GList *hbuf, int direction, const char *string)
+{
+  hbuf_block *blk;
+
+  for (;;) {
+    if (direction > 0)
+      hbuf = g_list_next(hbuf);
+    else
+      hbuf = g_list_previous(hbuf);
+
+    if (!hbuf) break;
+
+    blk = (hbuf_block*)(hbuf->data);
+    // XXX blk->ptr is (maybe) not really correct, because the match should
+    // not be after ptr_end.  We should check that...
+    if (strcasestr(blk->ptr, string))
+      break;
+  }
+
+  return hbuf;
+}
+
+//  hbuf_jump_date(hbuf, t)
+// Return a pointer to the first line after date t in the history buffer
+GList *hbuf_jump_date(GList *hbuf, time_t t)
+{
+  hbuf_block *blk;
+
+  hbuf = g_list_first(hbuf);
+
+  for ( ; hbuf && g_list_next(hbuf); hbuf = g_list_next(hbuf)) {
+    blk = (hbuf_block*)(hbuf->data);
+    if (blk->prefix.timestamp >= t) break;
+  }
+
+  return hbuf;
+}
+
+//  hbuf_jump_percent(hbuf, pc)
+// Return a pointer to the line at % pc of the history buffer
+GList *hbuf_jump_percent(GList *hbuf, int pc)
+{
+  guint hlen;
+
+  hbuf = g_list_first(hbuf);
+  hlen = g_list_length(hbuf);
+
+  return g_list_nth(hbuf, pc*hlen/100);
+}
+
+//  hbuf_dump_to_file(hbuf, filename)
+// Save the buffer to a file.
+void hbuf_dump_to_file(GList *hbuf, const char *filename)
+{
+  hbuf_block *blk;
+  hbb_line line;
+  guint last_persist_prefixflags = 0;
+  guint prefixwidth;
+  char pref[96];
+  FILE *fp;
+  struct stat statbuf;
+
+  if (!stat(filename, &statbuf)) {
+    scr_LogPrint(LPRINT_NORMAL, "The file already exists.");
+    return;
+  }
+  fp = fopen(filename, "w");
+  if (!fp) {
+    scr_LogPrint(LPRINT_NORMAL, "Unable to open the file.");
+    return;
+  }
+
+  prefixwidth = scr_getprefixwidth();
+  prefixwidth = MIN(prefixwidth, sizeof pref);
+
+  for (hbuf = g_list_first(hbuf); hbuf; hbuf = g_list_next(hbuf)) {
+    int maxlen;
+
+    blk = (hbuf_block*)(hbuf->data);
+    maxlen = blk->ptr_end - blk->ptr;
+
+    memset(&line, 0, sizeof(line));
+    line.timestamp  = blk->prefix.timestamp;
+    line.flags      = blk->prefix.flags;
+    line.mucnicklen = blk->prefix.mucnicklen;
+    line.text       = g_strndup(blk->ptr, maxlen);
+
+    if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) {
+      last_persist_prefixflags = blk->prefix.flags;
+    } else {
+      // Propagate highlighting flags
+      line.flags |= last_persist_prefixflags &
+                    (HBB_PREFIX_HLIGHT_OUT | HBB_PREFIX_HLIGHT |
+                     HBB_PREFIX_INFO | HBB_PREFIX_IN);
+      // Continuation of a message - omit the prefix
+      line.flags |= HBB_PREFIX_CONT;
+      line.mucnicklen = 0; // The nick is in the first one
+    }
+
+    scr_line_prefix(&line, pref, prefixwidth);
+    fprintf(fp, "%s%s\n", pref, line.text);
+  }
+
+  fclose(fp);
+  return;
+}
+
+//  hbuf_remove_receipt(hbuf, xep184)
+// Remove the Receipt Flag for the message with the given xep184 id
+// Returns TRUE if it was found and removed, otherwise FALSE
+gboolean hbuf_remove_receipt(GList *hbuf, gpointer xep184)
+{
+  hbuf_block *blk;
+
+  hbuf = g_list_first(hbuf);
+
+  for ( ; hbuf; hbuf = g_list_next(hbuf)) {
+    blk = (hbuf_block*)(hbuf->data);
+    if (blk->prefix.xep184 == xep184) {
+      blk->prefix.xep184 = NULL;
+      blk->prefix.flags ^= HBB_PREFIX_RECEIPT;
+      return TRUE;
+    }
+  }
+  return FALSE;
+}
+
+//  hbuf_get_blocks_number()
+// Returns the number of allocated hbuf_block's.
+guint hbuf_get_blocks_number(GList *hbuf)
+{
+  hbuf_block *hbuf_b_elt;
+  guint count = 0U;
+
+  for (hbuf = g_list_first(hbuf); hbuf; hbuf = g_list_next(hbuf)) {
+    hbuf_b_elt = (hbuf_block*)(hbuf->data);
+    if (hbuf_b_elt->flags & HBB_FLAG_ALLOC)
+      count++;
+  }
+  return count;
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */