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