1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
2 /*
3  * This file is part of GtkSourceView
4  *
5  * Copyright (C) 2010 - Ignacio Casal Quinteiro
6  * Copyright (C) 2014 - Sébastien Wilmet <swilmet@gnome.org>
7  *
8  * GtkSourceView is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * GtkSourceView is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this library; if not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 #include <glib.h>
27 #include <gio/gio.h>
28 #include <string.h>
29 #include "gtksourcebufferinputstream.h"
30 #include "gtksource-enumtypes.h"
31 
32 /* NOTE: never use async methods on this stream, the stream is just
33  * a wrapper around GtkTextBuffer api so that we can use GIO Stream
34  * methods, but the underlying code operates on a GtkTextBuffer, so
35  * there is no I/O involved and should be accessed only by the main
36  * thread.
37  */
38 
39 struct _GtkSourceBufferInputStreamPrivate
40 {
41 	GtkTextBuffer *buffer;
42 	GtkTextMark *pos;
43 	gint bytes_partial;
44 
45 	GtkSourceNewlineType newline_type;
46 
47 	guint newline_added : 1;
48 	guint is_initialized : 1;
49 	guint add_trailing_newline : 1;
50 };
51 
52 enum
53 {
54 	PROP_0,
55 	PROP_BUFFER,
56 	PROP_NEWLINE_TYPE,
57 	PROP_ADD_TRAILING_NEWLINE
58 };
59 
60 G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceBufferInputStream, _gtk_source_buffer_input_stream, G_TYPE_INPUT_STREAM);
61 
62 static gsize
get_new_line_size(GtkSourceBufferInputStream * stream)63 get_new_line_size (GtkSourceBufferInputStream *stream)
64 {
65 	switch (stream->priv->newline_type)
66 	{
67 		case GTK_SOURCE_NEWLINE_TYPE_CR:
68 		case GTK_SOURCE_NEWLINE_TYPE_LF:
69 			return 1;
70 
71 		case GTK_SOURCE_NEWLINE_TYPE_CR_LF:
72 			return 2;
73 
74 		default:
75 			g_warn_if_reached ();
76 			break;
77 	}
78 
79 	return 1;
80 }
81 
82 static const gchar *
get_new_line(GtkSourceBufferInputStream * stream)83 get_new_line (GtkSourceBufferInputStream *stream)
84 {
85 	switch (stream->priv->newline_type)
86 	{
87 		case GTK_SOURCE_NEWLINE_TYPE_LF:
88 			return "\n";
89 
90 		case GTK_SOURCE_NEWLINE_TYPE_CR:
91 			return "\r";
92 
93 		case GTK_SOURCE_NEWLINE_TYPE_CR_LF:
94 			return "\r\n";
95 
96 		default:
97 			g_warn_if_reached ();
98 			break;
99 	}
100 
101 	return "\n";
102 }
103 
104 static gsize
read_line(GtkSourceBufferInputStream * stream,gchar * outbuf,gsize space_left)105 read_line (GtkSourceBufferInputStream *stream,
106 	   gchar                      *outbuf,
107 	   gsize                       space_left)
108 {
109 	GtkTextIter start, next, end;
110 	gchar *buf;
111 	gint bytes; /* int since it's what iter_get_offset returns */
112 	gsize bytes_to_write, newline_size, read;
113 	const gchar *newline;
114 	gboolean is_last;
115 
116 	if (stream->priv->buffer == NULL)
117 	{
118 		return 0;
119 	}
120 
121 	gtk_text_buffer_get_iter_at_mark (stream->priv->buffer,
122 					  &start,
123 					  stream->priv->pos);
124 
125 	if (gtk_text_iter_is_end (&start))
126 	{
127 		return 0;
128 	}
129 
130 	end = next = start;
131 	newline = get_new_line (stream);
132 
133 	/* Check needed for empty lines */
134 	if (!gtk_text_iter_ends_line (&end))
135 	{
136 		gtk_text_iter_forward_to_line_end (&end);
137 	}
138 
139 	gtk_text_iter_forward_line (&next);
140 
141 	buf = gtk_text_iter_get_slice (&start, &end);
142 
143 	/* the bytes of a line includes also the newline, so with the
144 	   offsets we remove the newline and we add the new newline size */
145 	bytes = gtk_text_iter_get_bytes_in_line (&start) - stream->priv->bytes_partial;
146 
147 	/* bytes_in_line includes the newlines, so we remove that assuming that
148 	   they are single byte characters */
149 	bytes -= gtk_text_iter_get_offset (&next) - gtk_text_iter_get_offset (&end);
150 	is_last = gtk_text_iter_is_end (&end);
151 
152 	/* bytes_to_write contains the amount of bytes we would like to write.
153 	   This means its the amount of bytes in the line (without the newline
154 	   in the buffer) + the amount of bytes for the newline we want to
155 	   write (newline_size) */
156 	bytes_to_write = bytes;
157 
158 	/* do not add the new newline_size for the last line */
159 	newline_size = get_new_line_size (stream);
160 	if (!is_last)
161 	{
162 		bytes_to_write += newline_size;
163 	}
164 
165 	if (bytes_to_write > space_left)
166 	{
167 		gchar *ptr;
168 		gint char_offset;
169 		gint written;
170 		glong to_write;
171 
172 		/* Here the line does not fit in the buffer, we thus write
173 		   the amount of bytes we can still fit, storing the position
174 		   for the next read with the mark. Do not try to write the
175 		   new newline in this case, it will be handled in the next
176 		   iteration */
177 		to_write = MIN ((glong)space_left, bytes);
178 		ptr = buf;
179 		written = 0;
180 		char_offset = 0;
181 
182 		while (written < to_write)
183 		{
184 			gint w;
185 
186 			ptr = g_utf8_next_char (ptr);
187 			w = (ptr - buf);
188 			if (w > to_write)
189 			{
190 				break;
191 			}
192 			else
193 			{
194 				written = w;
195 				++char_offset;
196 			}
197 		}
198 
199 		memcpy (outbuf, buf, written);
200 
201 		/* Note: offset is one past what we wrote */
202 		gtk_text_iter_forward_chars (&start, char_offset);
203 		stream->priv->bytes_partial += written;
204 		read = written;
205 	}
206 	else
207 	{
208 		/* First just copy the bytes without the newline */
209 		memcpy (outbuf, buf, bytes);
210 
211 		/* Then add the newline, but not for the last line */
212 		if (!is_last)
213 		{
214 			memcpy (outbuf + bytes, newline, newline_size);
215 		}
216 
217 		start = next;
218 		stream->priv->bytes_partial = 0;
219 		read = bytes_to_write;
220 	}
221 
222 	gtk_text_buffer_move_mark (stream->priv->buffer,
223 				   stream->priv->pos,
224 				   &start);
225 
226 	g_free (buf);
227 	return read;
228 }
229 
230 static gssize
_gtk_source_buffer_input_stream_read(GInputStream * input_stream,void * buffer,gsize count,GCancellable * cancellable,GError ** error)231 _gtk_source_buffer_input_stream_read (GInputStream  *input_stream,
232 				      void          *buffer,
233 				      gsize          count,
234 				      GCancellable  *cancellable,
235 				      GError       **error)
236 {
237 	GtkSourceBufferInputStream *stream;
238 	GtkTextIter iter;
239 	gssize space_left, read, n;
240 
241 	stream = GTK_SOURCE_BUFFER_INPUT_STREAM (input_stream);
242 
243 	if (count < 6)
244 	{
245 		g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
246 				     "Not enougth space in destination");
247 		return -1;
248 	}
249 
250 	if (g_cancellable_set_error_if_cancelled (cancellable, error))
251 	{
252 		return -1;
253 	}
254 
255 	if (stream->priv->buffer == NULL)
256 	{
257 		return 0;
258 	}
259 
260 	/* Initialize the mark to the first char in the text buffer */
261 	if (!stream->priv->is_initialized)
262 	{
263 		gtk_text_buffer_get_start_iter (stream->priv->buffer, &iter);
264 		stream->priv->pos = gtk_text_buffer_create_mark (stream->priv->buffer,
265 								 NULL,
266 								 &iter,
267 								 FALSE);
268 
269 		stream->priv->is_initialized = TRUE;
270 	}
271 
272 	space_left = count;
273 	read = 0;
274 
275 	do
276 	{
277 		n = read_line (stream, (gchar *)buffer + read, space_left);
278 		read += n;
279 		space_left -= n;
280 	} while (space_left > 0 && n != 0 && stream->priv->bytes_partial == 0);
281 
282 	/* Make sure that non-empty files are always terminated with \n (see bug #95676).
283 	 * Note that we strip the trailing \n when loading the file */
284 	gtk_text_buffer_get_iter_at_mark (stream->priv->buffer,
285 					  &iter,
286 					  stream->priv->pos);
287 
288 	if (gtk_text_iter_is_end (&iter) &&
289 	    !gtk_text_iter_is_start (&iter) &&
290 	    stream->priv->add_trailing_newline)
291 	{
292 		gssize newline_size;
293 
294 		newline_size = get_new_line_size (stream);
295 
296 		if (space_left >= newline_size &&
297 		    !stream->priv->newline_added)
298 		{
299 			const gchar *newline;
300 
301 			newline = get_new_line (stream);
302 
303 			memcpy ((gchar *)buffer + read, newline, newline_size);
304 
305 			read += newline_size;
306 			stream->priv->newline_added = TRUE;
307 		}
308 	}
309 
310 	return read;
311 }
312 
313 static gboolean
_gtk_source_buffer_input_stream_close(GInputStream * input_stream,GCancellable * cancellable,GError ** error)314 _gtk_source_buffer_input_stream_close (GInputStream  *input_stream,
315 				       GCancellable  *cancellable,
316 				       GError       **error)
317 {
318 	GtkSourceBufferInputStream *stream = GTK_SOURCE_BUFFER_INPUT_STREAM (input_stream);
319 
320 	stream->priv->newline_added = FALSE;
321 
322 	if (stream->priv->is_initialized &&
323 	    stream->priv->buffer != NULL)
324 	{
325 		gtk_text_buffer_delete_mark (stream->priv->buffer, stream->priv->pos);
326 	}
327 
328 	return TRUE;
329 }
330 
331 static void
_gtk_source_buffer_input_stream_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)332 _gtk_source_buffer_input_stream_set_property (GObject      *object,
333 					      guint         prop_id,
334 					      const GValue *value,
335 					      GParamSpec   *pspec)
336 {
337 	GtkSourceBufferInputStream *stream = GTK_SOURCE_BUFFER_INPUT_STREAM (object);
338 
339 	switch (prop_id)
340 	{
341 		case PROP_BUFFER:
342 			g_assert (stream->priv->buffer == NULL);
343 			stream->priv->buffer = g_value_dup_object (value);
344 			break;
345 
346 		case PROP_NEWLINE_TYPE:
347 			stream->priv->newline_type = g_value_get_enum (value);
348 			break;
349 
350 		case PROP_ADD_TRAILING_NEWLINE:
351 			stream->priv->add_trailing_newline = g_value_get_boolean (value);
352 			break;
353 
354 		default:
355 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
356 			break;
357 	}
358 }
359 
360 static void
_gtk_source_buffer_input_stream_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)361 _gtk_source_buffer_input_stream_get_property (GObject    *object,
362 					  guint       prop_id,
363 					  GValue     *value,
364 					  GParamSpec *pspec)
365 {
366 	GtkSourceBufferInputStream *stream = GTK_SOURCE_BUFFER_INPUT_STREAM (object);
367 
368 	switch (prop_id)
369 	{
370 		case PROP_BUFFER:
371 			g_value_set_object (value, stream->priv->buffer);
372 			break;
373 
374 		case PROP_NEWLINE_TYPE:
375 			g_value_set_enum (value, stream->priv->newline_type);
376 			break;
377 
378 		case PROP_ADD_TRAILING_NEWLINE:
379 			g_value_set_boolean (value, stream->priv->add_trailing_newline);
380 			break;
381 
382 		default:
383 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
384 			break;
385 	}
386 }
387 
388 static void
_gtk_source_buffer_input_stream_dispose(GObject * object)389 _gtk_source_buffer_input_stream_dispose (GObject *object)
390 {
391 	GtkSourceBufferInputStream *stream = GTK_SOURCE_BUFFER_INPUT_STREAM (object);
392 
393 	g_clear_object (&stream->priv->buffer);
394 
395 	G_OBJECT_CLASS (_gtk_source_buffer_input_stream_parent_class)->dispose (object);
396 }
397 
398 static void
_gtk_source_buffer_input_stream_class_init(GtkSourceBufferInputStreamClass * klass)399 _gtk_source_buffer_input_stream_class_init (GtkSourceBufferInputStreamClass *klass)
400 {
401 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
402 	GInputStreamClass *stream_class = G_INPUT_STREAM_CLASS (klass);
403 
404 	gobject_class->get_property = _gtk_source_buffer_input_stream_get_property;
405 	gobject_class->set_property = _gtk_source_buffer_input_stream_set_property;
406 	gobject_class->dispose = _gtk_source_buffer_input_stream_dispose;
407 
408 	stream_class->read_fn = _gtk_source_buffer_input_stream_read;
409 	stream_class->close_fn = _gtk_source_buffer_input_stream_close;
410 
411 	g_object_class_install_property (gobject_class,
412 					 PROP_BUFFER,
413 					 g_param_spec_object ("buffer",
414 							      "GtkTextBuffer",
415 							      "",
416 							      GTK_TYPE_TEXT_BUFFER,
417 							      G_PARAM_READWRITE |
418 							      G_PARAM_CONSTRUCT_ONLY |
419 							      G_PARAM_STATIC_STRINGS));
420 
421 	/**
422 	 * GtkSourceBufferInputStream:newline-type:
423 	 *
424 	 * The :newline-type property determines what is considered
425 	 * as a line ending when reading complete lines from the stream.
426 	 */
427 	g_object_class_install_property (gobject_class,
428 					 PROP_NEWLINE_TYPE,
429 					 g_param_spec_enum ("newline-type",
430 							    "Newline type",
431 							    "",
432 							    GTK_SOURCE_TYPE_NEWLINE_TYPE,
433 							    GTK_SOURCE_NEWLINE_TYPE_LF,
434 							    G_PARAM_READWRITE |
435 							    G_PARAM_STATIC_STRINGS |
436 							    G_PARAM_CONSTRUCT_ONLY));
437 
438 	/**
439 	 * GtkSourceBufferInputStream:add-trailing-newline:
440 	 *
441 	 * The :add-trailing-newline property specifies whether or not to
442 	 * add a trailing newline when reading the buffer.
443 	 */
444 	g_object_class_install_property (gobject_class,
445 	                                 PROP_ADD_TRAILING_NEWLINE,
446 	                                 g_param_spec_boolean ("add-trailing-newline",
447 	                                                       "Add trailing newline",
448 	                                                       "",
449 	                                                       TRUE,
450 	                                                       G_PARAM_READWRITE |
451 	                                                       G_PARAM_STATIC_STRINGS |
452 	                                                       G_PARAM_CONSTRUCT_ONLY));
453 }
454 
455 static void
_gtk_source_buffer_input_stream_init(GtkSourceBufferInputStream * stream)456 _gtk_source_buffer_input_stream_init (GtkSourceBufferInputStream *stream)
457 {
458 	stream->priv = _gtk_source_buffer_input_stream_get_instance_private (stream);
459 }
460 
461 /**
462  * _gtk_source_buffer_input_stream_new:
463  * @buffer: a #GtkTextBuffer
464  *
465  * Reads the data from @buffer.
466  *
467  * Returns: a new input stream to read @buffer
468  */
469 GtkSourceBufferInputStream *
_gtk_source_buffer_input_stream_new(GtkTextBuffer * buffer,GtkSourceNewlineType type,gboolean add_trailing_newline)470 _gtk_source_buffer_input_stream_new (GtkTextBuffer        *buffer,
471 				     GtkSourceNewlineType  type,
472 				     gboolean              add_trailing_newline)
473 {
474 	g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
475 
476 	return g_object_new (GTK_SOURCE_TYPE_BUFFER_INPUT_STREAM,
477 			     "buffer", buffer,
478 			     "newline-type", type,
479 			     "add-trailing-newline", add_trailing_newline,
480 			     NULL);
481 }
482 
483 gsize
_gtk_source_buffer_input_stream_get_total_size(GtkSourceBufferInputStream * stream)484 _gtk_source_buffer_input_stream_get_total_size (GtkSourceBufferInputStream *stream)
485 {
486 	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER_INPUT_STREAM (stream), 0);
487 
488 	if (stream->priv->buffer == NULL)
489 	{
490 		return 0;
491 	}
492 
493 	return gtk_text_buffer_get_char_count (stream->priv->buffer);
494 }
495 
496 gsize
_gtk_source_buffer_input_stream_tell(GtkSourceBufferInputStream * stream)497 _gtk_source_buffer_input_stream_tell (GtkSourceBufferInputStream *stream)
498 {
499 	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER_INPUT_STREAM (stream), 0);
500 
501 	/* FIXME: is this potentially inefficient? If yes, we could keep
502 	   track of the offset internally, assuming the mark doesn't move
503 	   during the operation */
504 	if (!stream->priv->is_initialized ||
505 	    stream->priv->buffer == NULL)
506 	{
507 		return 0;
508 	}
509 	else
510 	{
511 		GtkTextIter iter;
512 
513 		gtk_text_buffer_get_iter_at_mark (stream->priv->buffer,
514 						  &iter,
515 						  stream->priv->pos);
516 		return gtk_text_iter_get_offset (&iter);
517 	}
518 }
519