/* This is -*- C -*- */
/* $Id: guppi-stream.c,v 1.17 2001/09/08 05:50:00 trow Exp $ */
/*
* guppi-stream.c
*
* Copyright (C) 1999 EMC Capital Management, Inc.
*
* Developed by Jon Trowbridge <trow@gnu.org> and
* Havoc Pennington <hp@pobox.com>.
*
* 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 <config.h>
#include "guppi-stream.h"
#include <sys/stat.h>
#include <string.h>
#include <gtk/gtksignal.h>
#include <guppi-convenient.h>
static GtkObjectClass *parent_class = NULL;
enum {
PRELOAD,
FULLY_PRELOADED,
CHANGED_CODES,
LAST_SIGNAL
};
static guint str_signals[LAST_SIGNAL] = { 0 };
static void
guppi_stream_finalize (GtkObject *obj)
{
GuppiStream *gs = GUPPI_STREAM (obj);
gint i;
guppi_finalized (obj);
if (gs->in) {
guppi_file_close (gs->in);
guppi_unref0 (gs->in);
}
if (gs->buffer) {
for (i = 0; i <= gs->buffer_top; ++i) {
guppi_free (gs->buffer[i]);
guppi_free (gs->marked_buffer[i]);
guppi_free (gs->sani_buffer[i]);
}
guppi_free (gs->buffer);
guppi_free (gs->marked_buffer);
guppi_free (gs->sani_buffer);
}
guppi_free (gs->current_line);
guppi_free (gs->current_marked_line);
guppi_free (gs->current_sani_line);
guppi_free (gs->eol_comment);
guppi_free (gs->ml_start_comment);
guppi_free (gs->ml_end_comment);
if (parent_class->finalize)
parent_class->finalize (obj);
}
static void
guppi_stream_class_init (GuppiStreamClass *klass)
{
GtkObjectClass *object_class = (GtkObjectClass *) klass;
parent_class = gtk_type_class (GTK_TYPE_OBJECT);
str_signals[PRELOAD] =
gtk_signal_new ("preload",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (GuppiStreamClass, preload),
gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0);
str_signals[FULLY_PRELOADED] =
gtk_signal_new ("fully_preloaded",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (GuppiStreamClass, fully_preloaded),
gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0);
str_signals[CHANGED_CODES] =
gtk_signal_new ("changed_codes",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (GuppiStreamClass, changed_codes),
gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0);
gtk_object_class_add_signals (object_class, str_signals, LAST_SIGNAL);
klass->preload = NULL;
klass->fully_preloaded = NULL;
klass->changed_codes = NULL;
object_class->finalize = guppi_stream_finalize;
}
static void
guppi_stream_init (GuppiStream *gs)
{
gs->in = NULL;
gs->bad = FALSE;
gs->buffering = TRUE;
gs->eof = FALSE;
gs->current_line_no = -1;
gs->current_line = NULL;
gs->current_marked_line = NULL;
gs->current_sani_line = NULL;
gs->last_visited_line_no = -1;
gs->buffer_count = 0;
gs->buffer_top = 0;
gs->buffer = NULL;
gs->marked_buffer = NULL;
gs->sani_buffer = NULL;
gs->total_size = -1;
gs->buffered_size = 0;
gs->in_ml_comment = FALSE;
gs->eol_comment = guppi_strdup ("//");
gs->ml_start_comment = guppi_strdup ("/*");
gs->ml_end_comment = guppi_strdup ("*/");
gs->start_quote = '"';
gs->end_quote = '"';
gs->escape = '~';
}
GtkType
guppi_stream_get_type (void)
{
static GtkType guppi_stream_type = 0;
if (!guppi_stream_type) {
static const GtkTypeInfo guppi_stream_info = {
"GuppiStream",
sizeof (GuppiStream),
sizeof (GuppiStreamClass),
(GtkClassInitFunc) guppi_stream_class_init,
(GtkObjectInitFunc) guppi_stream_init,
NULL, NULL, (GtkClassInitFunc) NULL
};
guppi_stream_type = gtk_type_unique (GTK_TYPE_OBJECT, &guppi_stream_info);
}
return guppi_stream_type;
}
GuppiStream *
guppi_stream_new (GuppiFile *f)
{
GuppiStream *gs;
struct stat stat;
g_return_val_if_fail (f != NULL, NULL);
gs = GUPPI_STREAM (guppi_type_new (guppi_stream_get_type ()));
gs->in = f;
guppi_ref (gs->in);
if (guppi_file_stat (gs->in, &stat) == 0) {
gs->total_size = stat.st_size;
}
return gs;
}
GuppiStream *
guppi_stream_open_file (const gchar *filename)
{
GuppiFile *in;
GuppiStream *gs;
g_return_val_if_fail (filename != NULL, NULL);
in = guppi_file_open (filename);
if (in == NULL)
return NULL;
gs = guppi_stream_new (in);
guppi_unref (in);
return gs;
}
/**********************************************************************/
static gboolean
guppi_stream_mark_line (GuppiStream *gs,
const gchar *line, gchar *buffer, gint buffer_len)
{
gboolean injected_escape, in_comment, in_quote, punch_in;
gint p = 0, i, j;
gint eol_comment_len;
gint ml_start_comment_len;
gint ml_end_comment_len;
g_return_val_if_fail (gs != NULL, FALSE);
g_return_val_if_fail (line != NULL, FALSE);
g_return_val_if_fail (buffer_len > 0, FALSE);
injected_escape = FALSE;
eol_comment_len = gs->eol_comment ? strlen (gs->eol_comment) : 0;
ml_start_comment_len =
gs->ml_start_comment ? strlen (gs->ml_start_comment) : 0;
ml_end_comment_len = gs->ml_end_comment ? strlen (gs->ml_end_comment) : 0;
if (gs->in_ml_comment) {
buffer[0] = gs->escape;
buffer[1] = 'C';
p += 2;
injected_escape = TRUE;
}
in_comment = gs->in_ml_comment;
in_quote = FALSE;
i = 0;
while (line[i]) {
punch_in = TRUE;
if (line[i] == gs->escape) {
buffer[p] = gs->escape;
++p;
injected_escape = TRUE;
} else if (in_quote) {
in_quote = line[i] != gs->end_quote;
} else if (line[i] == gs->start_quote) {
in_quote = TRUE;
} else if (!in_quote &&
!in_comment &&
eol_comment_len &&
!strncmp (gs->eol_comment, line + i, eol_comment_len)) {
/* We've hit an end-of-line-comment, so adjust accordingly */
buffer[p] = gs->escape;
buffer[p + 1] = 'L';
p += 2;
while (line[i])
buffer[p++] = line[i++];
injected_escape = TRUE;
punch_in = FALSE;
} else if (!in_quote &&
!in_comment &&
ml_start_comment_len &&
!strncmp (gs->ml_start_comment, line + i,
ml_start_comment_len)) {
buffer[p] = gs->escape;
buffer[p + 1] = 'C';
p += 2;
in_comment = TRUE;
injected_escape = TRUE;
} else if (in_comment &&
!in_quote &&
ml_end_comment_len &&
!strncmp (gs->ml_end_comment, line + i, ml_end_comment_len)) {
for (j = 0; j < ml_end_comment_len; ++j)
buffer[p++] = line[i++];
buffer[p] = gs->escape;
buffer[p + 1] = 'c';
p += 2;
in_comment = FALSE;
injected_escape = TRUE;
punch_in = FALSE;
}
if (punch_in) {
buffer[p] = line[i];
++p;
++i;
}
}
gs->in_ml_comment = in_comment;
buffer[p] = '\0';
/* Return whether or not we did anything */
return injected_escape;
}
static gboolean
guppi_stream_sanitize_line (GuppiStream *gs,
const gchar *line, gchar *sani, gint sani_len)
{
gint i = 0, j = 0;
gboolean seen_escape;
gboolean pending = FALSE, in_comment = FALSE, done = FALSE;
g_return_val_if_fail (gs != NULL, FALSE);
g_return_val_if_fail (line != NULL, FALSE);
g_return_val_if_fail (sani != NULL, FALSE);
g_return_val_if_fail (sani_len > 0, FALSE);
seen_escape = FALSE;
i = 0;
while (line[i] && !seen_escape) {
if (line[i] == gs->escape)
seen_escape = TRUE;
++i;
}
if (!seen_escape)
return FALSE;
i = 0;
while (line[i] && !done) {
if (pending) {
if (line[i] == gs->escape) {
if (!in_comment)
sani[j++] = gs->escape;
} else if (line[i] == 'C')
in_comment = TRUE;
else if (line[i] == 'c')
in_comment = FALSE;
else if (line[i] == 'L')
done = TRUE;
else
g_assert_not_reached ();
pending = FALSE;
} else {
if (line[i] == gs->escape)
pending = TRUE;
else if (!in_comment)
sani[j++] = line[i];
}
++i;
}
sani[j] = '\0';
return TRUE;
}
/**********************************************************************/
static void
guppi_stream_changed (GuppiStream *gs)
{
const gint buflen = 1024;
gchar mbuf[1024];
gchar sbuf[1024];
gboolean did_mark, did_sani;
gint i;
g_return_if_fail (gs != NULL);
g_assert (gs->buffering);
/* Reset state */
gs->in_ml_comment = FALSE;
/* Regenerate buffered marked/sani lines and reset state */
for (i = 0; i <= gs->buffer_top; ++i) {
guppi_free (gs->marked_buffer[i]);
guppi_free (gs->sani_buffer[i]);
did_mark = guppi_stream_mark_line (gs, gs->buffer[i], mbuf, buflen);
did_sani = did_mark
&& guppi_stream_sanitize_line (gs, mbuf, sbuf, buflen);
gs->marked_buffer[i] = did_mark ? guppi_strdup (mbuf) : NULL;
gs->sani_buffer[i] = did_sani ? guppi_strdup (sbuf) : NULL;
}
gtk_signal_emit (GTK_OBJECT (gs), str_signals[CHANGED_CODES]);
}
gboolean
guppi_stream_sequential_mode (GuppiStream *gs)
{
g_return_val_if_fail (gs != NULL, FALSE);
return !gs->buffering;
}
void
guppi_stream_set_sequential_mode (GuppiStream *gs)
{
g_return_if_fail (gs != NULL);
g_return_if_fail (gs->buffering);
gs->buffering = FALSE;
}
const gchar *
guppi_stream_eol_comment (const GuppiStream *gs)
{
g_return_val_if_fail (gs != NULL, NULL);
return gs->eol_comment;
}
void
guppi_stream_set_eol_comment (GuppiStream *gs, const gchar *x)
{
g_return_if_fail (gs != NULL);
if (x == NULL && gs->eol_comment == NULL)
return;
if ((x == NULL && gs->eol_comment != NULL) ||
(x != NULL && gs->eol_comment == NULL) ||
(strcmp (x, gs->eol_comment) != 0)) {
guppi_free (gs->eol_comment);
gs->eol_comment = guppi_strdup (x);
guppi_stream_changed (gs);
}
}
const gchar *
guppi_stream_ml_comment_start (const GuppiStream *gs)
{
g_return_val_if_fail (gs != NULL, NULL);
return gs->ml_start_comment;
}
const gchar *
guppi_stream_ml_comment_end (const GuppiStream *gs)
{
g_return_val_if_fail (gs != NULL, NULL);
return gs->ml_end_comment;
}
void
guppi_stream_set_ml_comment_start (GuppiStream *gs, const gchar *x)
{
g_return_if_fail (gs != NULL);
if (x == NULL && gs->ml_start_comment == NULL)
return;
if ((x == NULL && gs->ml_start_comment != NULL) ||
(x != NULL && gs->ml_start_comment == NULL) ||
(strcmp (x, gs->ml_start_comment) != 0)) {
guppi_free (gs->ml_start_comment);
gs->ml_start_comment = guppi_strdup (x);
guppi_stream_changed (gs);
}
}
void
guppi_stream_set_ml_comment_end (GuppiStream *gs, const gchar *x)
{
g_return_if_fail (gs != NULL);
if (x == NULL && gs->ml_end_comment == NULL)
return;
if ((x == NULL && gs->ml_end_comment != NULL) ||
(x != NULL && gs->ml_end_comment == NULL) ||
(strcmp (x, gs->ml_end_comment) != 0)) {
guppi_free (gs->ml_end_comment);
gs->ml_end_comment = guppi_strdup (x);
guppi_stream_changed (gs);
}
}
gchar
guppi_stream_quote_start (const GuppiStream *gs)
{
g_return_val_if_fail (gs != NULL, '\0');
return gs->start_quote;
}
gchar
guppi_stream_quote_end (const GuppiStream *gs)
{
g_return_val_if_fail (gs != NULL, '\0');
return gs->end_quote;
}
void
guppi_stream_set_quote_start (GuppiStream *gs, gchar x)
{
g_return_if_fail (gs != NULL);
gs->start_quote = x;
guppi_stream_changed (gs);
}
void
guppi_stream_set_quote_end (GuppiStream *gs, gchar x)
{
g_return_if_fail (gs != NULL);
gs->end_quote = x;
guppi_stream_changed (gs);
}
gchar
guppi_stream_escape (const GuppiStream *gs)
{
g_return_val_if_fail (gs != NULL, '\0');
return gs->escape;
}
const gchar *
guppi_stream_source (GuppiStream *gs)
{
g_return_val_if_fail (gs != NULL, NULL);
return guppi_file_filename (gs->in);
}
gint
guppi_stream_number_of_lines (GuppiStream *gs)
{
g_return_val_if_fail (gs != NULL, -1);
return gs->buffering && gs->eof ? 1 + gs->current_line_no : -1;
}
gint
guppi_stream_estimated_number_of_lines (GuppiStream *gs)
{
g_return_val_if_fail (gs != NULL, -1);
if (gs->buffering && gs->eof)
return 1 + gs->current_line_no;
if (gs->total_size > 0) {
if (gs->current_line > 0 && gs->buffered_size > 0)
return (gint) (gs->total_size /
(gs->buffered_size / (1.0 + gs->current_line_no)));
else
return gs->total_size / 60; /* totally arbitrary */
}
return -1;
}
int
guppi_stream_number_of_preloaded_lines (GuppiStream *gs)
{
g_return_val_if_fail (gs != NULL, -1);
return gs->buffering ? gs->buffer_top : -1;
}
/* Ensure that the buffer is big enough to accomodate at least minimum_count
lines. */
static void
guppi_stream_grow_buffers (GuppiStream *gs, gint minimum_count)
{
gint new_count;
gchar **new_buffer;
g_return_if_fail (gs != NULL);
g_return_if_fail (minimum_count > 0);
if (gs->buffer_count >= minimum_count)
return;
/* Find the new size for our buffers */
new_count = gs->buffer_count ? gs->buffer_count : 500;
while (new_count < minimum_count)
new_count *= 2;
/* Grow our three buffers (standard, marked, sanitized) */
new_buffer = guppi_new0 (gchar *, new_count);
memcpy (new_buffer, gs->buffer, gs->buffer_count * sizeof (gchar *));
guppi_free (gs->buffer);
gs->buffer = new_buffer;
new_buffer = guppi_new0 (gchar *, new_count);
memcpy (new_buffer, gs->marked_buffer, gs->buffer_count * sizeof (gchar *));
guppi_free (gs->marked_buffer);
gs->marked_buffer = new_buffer;
new_buffer = guppi_new0 (gchar *, new_count);
memcpy (new_buffer, gs->sani_buffer, gs->buffer_count * sizeof (gchar *));
guppi_free (gs->sani_buffer);
gs->sani_buffer = new_buffer;
gs->buffer_count = new_count;
}
static void
guppi_stream_add_to_buffer (GuppiStream *gs,
const gchar *line,
const gchar *marked_line,
const gchar *sani_line)
{
gint i;
g_return_if_fail (gs != NULL);
g_return_if_fail (gs->buffering);
i = gs->current_line_no;
guppi_stream_grow_buffers (gs, 1 + i);
gs->buffer[i] = guppi_strdup (line);
gs->marked_buffer[i] = guppi_strdup (marked_line);
gs->sani_buffer[i] = guppi_strdup (sani_line);
gs->buffered_size += strlen (line) + 1;
gs->buffer_top = gs->current_line_no;
}
/**********************************************************************/
static void
guppi_stream_get_lines_through (GuppiStream *gs, gint line_no)
{
const gint buflen = 1024;
gchar buf[1024];
gchar mbuf[1024];
gchar sbuf[1024];
gboolean did_mark, did_sani;
gboolean did_something = FALSE;
g_return_if_fail (gs != NULL);
g_return_if_fail (line_no >= 0);
g_return_if_fail (!gs->bad);
if (gs->eof)
return;
while (!gs->eof && gs->current_line_no < line_no) {
if (guppi_file_gets (gs->in, buf, buflen)) {
g_strchomp (buf);
did_something = TRUE;
++gs->current_line_no;
did_mark = guppi_stream_mark_line (gs, buf, mbuf, buflen);
did_sani = did_mark &&
guppi_stream_sanitize_line (gs, mbuf, sbuf, buflen);
if (gs->buffering)
guppi_stream_add_to_buffer (gs,
buf,
did_mark ? mbuf : NULL,
did_sani ? sbuf : NULL);
if (gs->current_line_no == line_no) {
guppi_free (gs->current_line);
gs->current_line = guppi_strdup (buf);
guppi_free (gs->current_marked_line);
gs->current_marked_line = did_mark ? guppi_strdup (mbuf) : NULL;
guppi_free (gs->current_sani_line);
gs->current_sani_line = did_sani ? guppi_strdup (sbuf) : NULL;
}
} else {
gs->eof = TRUE;
/*
guppi_file_close(gs->in);
gtk_object_unref(GTK_OBJECT(gs->in));
gs->in = NULL;
*/
if (gs->buffering) {
gtk_signal_emit (GTK_OBJECT (gs), str_signals[FULLY_PRELOADED]);
if (gs->total_size < 0)
gs->total_size = gs->buffered_size;
}
}
}
if (did_something)
gtk_signal_emit (GTK_OBJECT (gs), str_signals[PRELOAD]);
}
static void
guppi_stream_line_get_triple (GuppiStream *gs, gint line_no,
const gchar ** line,
const gchar ** marked, const gchar ** sani)
{
g_return_if_fail (gs != NULL);
g_return_if_fail (line_no >= 0);
g_return_if_fail (!gs->bad);
if (!gs->buffering) {
if (line_no < gs->last_visited_line_no) {
g_warning ("buffering violation: %d/%d", line_no, gs->current_line_no);
return;
}
gs->last_visited_line_no = line_no;
}
if (line_no <= gs->buffer_top) {
if (line)
*line = gs->buffer[line_no];
if (marked) {
*marked = gs->marked_buffer[line_no];
if (*marked == NULL)
*marked = gs->buffer[line_no];
}
if (sani) {
*sani = gs->sani_buffer[line_no];
if (*sani == NULL)
*sani = gs->buffer[line_no];
}
return;
}
if (gs->current_line_no < line_no)
guppi_stream_get_lines_through (gs, line_no);
if (gs->current_line_no == line_no) {
if (line)
*line = gs->current_line;
if (marked) {
*marked = gs->current_marked_line;
if (*marked == NULL)
*marked = gs->current_line;
}
if (sani) {
*sani = gs->current_sani_line;
if (*sani == NULL)
*sani = gs->current_line;
}
return;
}
if (line)
*line = NULL;
if (marked)
*marked = NULL;
if (sani)
*sani = NULL;
}
const gchar *
guppi_stream_get_line (GuppiStream *gs, gint line_no)
{
const gchar *x;
guppi_stream_line_get_triple (gs, line_no, &x, NULL, NULL);
return x;
}
const gchar *
guppi_stream_get_marked_line (GuppiStream *gs, gint line_no)
{
const gchar *x;
guppi_stream_line_get_triple (gs, line_no, NULL, &x, NULL);
return x;
}
const gchar *
guppi_stream_get_sanitized_line (GuppiStream *gs, gint line_no)
{
const gchar *x;
guppi_stream_line_get_triple (gs, line_no, NULL, NULL, &x);
return x;
}
/**********************************************************************/
void
guppi_stream_load_some_lines (GuppiStream *gs)
{
g_return_if_fail (gs != NULL);
if (gs->current_line_no < 0)
guppi_stream_get_lines_through (gs, 250);
}
void
guppi_stream_load_more_lines (GuppiStream *gs)
{
g_return_if_fail (gs != NULL);
guppi_stream_get_lines_through (gs, gs->current_line_no + 250);
}
/* $Id: guppi-stream.c,v 1.17 2001/09/08 05:50:00 trow Exp $ */
syntax highlighted by Code2HTML, v. 0.9.1