1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3 * anjuta-token-file.c
4 * Copyright (C) Sébastien Granjoux 2009 <seb.sfo@free.fr>
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or
9 * (at 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.
14 * See the GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "anjuta-token-file.h"
21
22 #include "anjuta-debug.h"
23
24 #include <glib-object.h>
25
26 #include <stdio.h>
27 #include <string.h>
28
29 /* Types declarations
30 *---------------------------------------------------------------------------*/
31
32 struct _AnjutaTokenFile
33 {
34 GObject parent;
35
36 GFile* file; /* Corresponding GFile */
37
38 AnjutaToken *content; /* Current file content */
39
40 AnjutaToken *save; /* List of memory block used */
41
42 gboolean dirty; /* Set when the file has been modified */
43 };
44
45 struct _AnjutaTokenFileClass
46 {
47 GObjectClass parent_class;
48 };
49
50 static GObjectClass *parent_class = NULL;
51
52 /* Helpers functions
53 *---------------------------------------------------------------------------*/
54
55 /* Private functions
56 *---------------------------------------------------------------------------*/
57
58 static AnjutaToken*
anjuta_token_file_find_position(AnjutaTokenFile * file,AnjutaToken * token)59 anjuta_token_file_find_position (AnjutaTokenFile *file, AnjutaToken *token)
60 {
61 AnjutaToken *start;
62 const gchar *pos;
63 const gchar *ptr;
64 const gchar *end;
65
66 if (token == NULL) return NULL;
67
68 if (anjuta_token_get_length (token) == 0)
69 {
70 AnjutaToken *last = anjuta_token_last (token);
71
72 for (; (token != NULL) && (token != last); token = anjuta_token_next (token))
73 {
74 if (anjuta_token_get_length (token) != 0) break;
75 }
76
77 if (anjuta_token_get_length (token) == 0) return NULL;
78 }
79
80 pos = anjuta_token_get_string (token);
81 for (start = file->content; start != NULL; start = anjuta_token_next (start))
82 {
83 guint len = anjuta_token_get_length (start);
84
85 if (len)
86 {
87 ptr = anjuta_token_get_string (start);
88 end = ptr + len;
89
90 if ((pos >= ptr) && (pos < end)) break;
91 }
92 }
93 if ((start != NULL) && (ptr != pos))
94 {
95 start = anjuta_token_split (start, pos - ptr);
96 start = anjuta_token_next (start);
97 }
98
99 return start;
100 }
101
102 /* Public functions
103 *---------------------------------------------------------------------------*/
104
105 AnjutaToken*
anjuta_token_file_load(AnjutaTokenFile * file,GError ** error)106 anjuta_token_file_load (AnjutaTokenFile *file, GError **error)
107 {
108 gchar *content;
109 gsize length;
110
111 anjuta_token_file_unload (file);
112
113 if (g_file_load_contents (file->file, NULL, &content, &length, NULL, error))
114 {
115 AnjutaToken *token;
116
117 file->save = anjuta_token_new_static (ANJUTA_TOKEN_FILE, NULL);
118 file->content = anjuta_token_new_static (ANJUTA_TOKEN_FILE, NULL);
119
120 token = anjuta_token_new_string_len (ANJUTA_TOKEN_FILE, content, length);
121 anjuta_token_prepend_child (file->save, token);
122
123 token = anjuta_token_new_static (ANJUTA_TOKEN_FILE, content);
124 anjuta_token_prepend_child (file->content, token);
125 file->dirty = FALSE;
126 }
127
128 return file->content;
129 }
130
131 gboolean
anjuta_token_file_unload(AnjutaTokenFile * file)132 anjuta_token_file_unload (AnjutaTokenFile *file)
133 {
134 if (file->content != NULL) anjuta_token_free (file->content);
135 file->content = NULL;
136
137 if (file->save != NULL) anjuta_token_free (file->save);
138 file->save = NULL;
139
140 return TRUE;
141 }
142
143 gboolean
anjuta_token_file_save(AnjutaTokenFile * file,GError ** error)144 anjuta_token_file_save (AnjutaTokenFile *file, GError **error)
145 {
146 GFileOutputStream *stream;
147 gboolean ok = TRUE;
148 GError *err = NULL;
149 AnjutaToken *token;
150
151 stream = g_file_replace (file->file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &err);
152 if (stream == NULL)
153 {
154 if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
155 {
156 /* Perhaps parent directory is missing, try to create it */
157 GFile *parent = g_file_get_parent (file->file);
158
159 if (g_file_make_directory_with_parents (parent, NULL, NULL))
160 {
161 g_object_unref (parent);
162 g_clear_error (&err);
163 stream = g_file_replace (file->file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error);
164 if (stream == NULL) return FALSE;
165 }
166 else
167 {
168 g_object_unref (parent);
169 g_propagate_error (error, err);
170
171 return FALSE;
172 }
173 }
174 else
175 {
176 g_propagate_error (error, err);
177 return FALSE;
178 }
179 }
180
181 for (token = file->content; token != NULL; token = anjuta_token_next (token))
182 {
183 if (!(anjuta_token_get_flags (token) & ANJUTA_TOKEN_REMOVED) && (anjuta_token_get_length (token)))
184 {
185 if (g_output_stream_write (G_OUTPUT_STREAM (stream), anjuta_token_get_string (token), anjuta_token_get_length (token) * sizeof (char), NULL, error) < 0)
186 {
187 ok = FALSE;
188 break;
189 }
190 }
191 }
192
193 ok = ok && g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, NULL);
194 g_object_unref (stream);
195 file->dirty = FALSE;
196
197 return ok;
198 }
199
200 void
anjuta_token_file_move(AnjutaTokenFile * file,GFile * new_file)201 anjuta_token_file_move (AnjutaTokenFile *file, GFile *new_file)
202 {
203 if (file->file) g_object_unref (file->file);
204 file->file = new_file != NULL ? g_object_ref (new_file) : NULL;
205 file->dirty = new_file != NULL;
206 }
207
208 static void
remove_raw_token(AnjutaToken * token,gpointer user_data)209 remove_raw_token (AnjutaToken *token, gpointer user_data)
210 {
211 AnjutaTokenFile *file = (AnjutaTokenFile *)user_data;
212
213 if ((anjuta_token_get_length (token) > 0))
214 {
215 AnjutaToken *pos = anjuta_token_file_find_position (file, token);
216 guint len = anjuta_token_get_length (token);
217
218 if (pos != NULL)
219 {
220 while (len != 0)
221 {
222 guint flen = anjuta_token_get_length (pos);
223 if (len < flen)
224 {
225 pos = anjuta_token_split (pos, len);
226 flen = len;
227 }
228 pos = anjuta_token_free (pos);
229 len -= flen;
230 }
231 }
232 }
233 }
234
235 static AnjutaToken *
anjuta_token_file_remove_token(AnjutaTokenFile * file,AnjutaToken * token)236 anjuta_token_file_remove_token (AnjutaTokenFile *file, AnjutaToken *token)
237 {
238 AnjutaToken *next = NULL;
239
240 if (token != NULL) next = anjuta_token_foreach_post_order (token, remove_raw_token, file);
241 file->dirty = TRUE;
242
243 return next;
244 }
245
246 /**
247 * anjuta_token_file_update:
248 * @file: a #AnjutaTokenFile derived class object.
249 * @token: Token to update.
250 *
251 * Update the file with all changed token starting from @token. The function can
252 * return an error if the token is not in the file.
253 *
254 * Return value: %TRUE is the update is done without error.
255 */
256 gboolean
anjuta_token_file_update(AnjutaTokenFile * file,AnjutaToken * token)257 anjuta_token_file_update (AnjutaTokenFile *file, AnjutaToken *token)
258 {
259 AnjutaToken *prev;
260 AnjutaToken *next;
261 AnjutaToken *last;
262 guint added;
263 gchar *value;
264
265 /* Find all token needing an update */
266
267 /* Find following tokens */
268 for (last = token; last != NULL; last = anjuta_token_next (last))
269 {
270 /* Get all tokens in group */
271 last = anjuta_token_last (last);
272
273 gint flags = anjuta_token_get_flags (last);
274 if (!(flags & (ANJUTA_TOKEN_ADDED | ANJUTA_TOKEN_REMOVED))) break;
275 }
276
277 /* Find first modified token */
278 for (;;)
279 {
280 gint flags = anjuta_token_get_flags (token);
281 if (flags & (ANJUTA_TOKEN_ADDED | ANJUTA_TOKEN_REMOVED))
282 {
283 /* Check previous token */
284 for (prev = token; prev != NULL; prev = anjuta_token_previous (prev))
285 {
286 gint flags = anjuta_token_get_flags (prev);
287 if (!(flags & (ANJUTA_TOKEN_ADDED | ANJUTA_TOKEN_REMOVED))) break;
288 token = prev;
289 }
290
291 break;
292 }
293 if (token == last)
294 {
295 /* No changed */
296 return TRUE;
297 }
298 token = anjuta_token_next (token);
299 }
300
301 /* Find previous token */
302 for (prev = token; prev != NULL; prev = anjuta_token_previous (prev))
303 {
304 gint flags = anjuta_token_get_flags (prev);
305 if ((anjuta_token_get_length (prev) != 0) && !(flags & (ANJUTA_TOKEN_ADDED | ANJUTA_TOKEN_REMOVED))) break;
306 }
307
308 /* Delete removed token and compute length of added token */
309 added = 0;
310 for (next = token; (next != NULL) && (next != last);)
311 {
312 gint flags = anjuta_token_get_flags (next);
313
314 if (flags & ANJUTA_TOKEN_ADDED)
315 {
316 added += anjuta_token_get_length (next);
317 }
318 next = anjuta_token_next (next);
319 }
320
321 /* Add new token */
322 if (added != 0)
323 {
324 AnjutaToken *add;
325 AnjutaToken *start = NULL;
326
327 value = g_new (gchar, added);
328 add = anjuta_token_prepend_child (file->save, anjuta_token_new_string_len (ANJUTA_TOKEN_NAME, value, added));
329
330 /* Find token position */
331 if (prev != NULL)
332 {
333 start = anjuta_token_file_find_position (file, prev);
334 if (start != NULL) start = anjuta_token_split (start, anjuta_token_get_length (prev));
335 }
336
337 /* Insert token */
338 add = anjuta_token_new_static_len (ANJUTA_TOKEN_NAME, value, added);
339 if (start == NULL)
340 {
341 anjuta_token_prepend_child (file->content, add);
342 }
343 else
344 {
345 anjuta_token_insert_after (start, add);
346 }
347 }
348
349 for (next = token; (next != NULL) && (next != last);)
350 {
351 gint flags = anjuta_token_get_flags (next);
352
353 if (flags & ANJUTA_TOKEN_ADDED)
354 {
355 guint len = anjuta_token_get_length (next);
356
357 if (len > 0)
358 {
359 memcpy(value, anjuta_token_get_string (next), len);
360 anjuta_token_set_string (next, value, len);
361 value += len;
362 }
363 anjuta_token_clear_flags (next, ANJUTA_TOKEN_ADDED);
364 }
365 else if (flags & ANJUTA_TOKEN_REMOVED)
366 {
367 next = anjuta_token_file_remove_token (file, next);
368 continue;
369 }
370 next = anjuta_token_next (next);
371 }
372
373 file->dirty = TRUE;
374
375 return TRUE;
376 }
377
378 /**
379 * anjuta_token_file_get_token_position:
380 * @file: #AnjutaTokenFile object
381 * @token: token
382 *
383 * Returns the position of the token in the file. This position is a number
384 * which doesn't correspond to a line number or a character but respect the
385 * order of token in the file.
386 *
387 * Returns: The position of the token or 0 if the token is not in the file.
388 */
389 gsize
anjuta_token_file_get_token_position(AnjutaTokenFile * file,AnjutaToken * token)390 anjuta_token_file_get_token_position (AnjutaTokenFile *file, AnjutaToken *token)
391 {
392 AnjutaToken *content;
393 const gchar *string;
394 gsize pos = 1;
395
396
397 do
398 {
399 string = anjuta_token_get_string (token);
400 if (string != NULL) break;
401
402 /* token is a group or an empty token, looks for group members or
403 * following token */
404 token = anjuta_token_next_after_children (token);
405 } while (token != NULL);
406
407 for (content = file->content; content != NULL; content = anjuta_token_next (content))
408 {
409 const gchar *start;
410 gsize len;
411
412 start = anjuta_token_get_string (content);
413 len = anjuta_token_get_length (content);
414
415 if ((string >= start) && ((string - start) < len))
416 {
417 pos += string - start;
418 break;
419 }
420 else
421 {
422 pos += len;
423 }
424 }
425
426 return content == NULL ? 0 : pos;
427 }
428
429
430 gboolean
anjuta_token_file_get_token_location(AnjutaTokenFile * file,AnjutaTokenFileLocation * location,AnjutaToken * token)431 anjuta_token_file_get_token_location (AnjutaTokenFile *file, AnjutaTokenFileLocation *location, AnjutaToken *token)
432 {
433 AnjutaTokenFileLocation loc = {NULL, 1, 1};
434 AnjutaToken *pos;
435 const gchar *target;
436
437 anjuta_token_dump (token);
438 do
439 {
440 target = anjuta_token_get_string (token);
441 if (target != NULL) break;
442
443 /* token is a group or an empty token, looks for group members or
444 * following token */
445 token = anjuta_token_next_after_children (token);
446 } while (token != NULL);
447
448 for (pos = file->content; pos != NULL; pos = anjuta_token_next (pos))
449 {
450 if (!(anjuta_token_get_flags (pos) & ANJUTA_TOKEN_REMOVED) && (anjuta_token_get_length (pos) > 0))
451 {
452 const gchar *ptr;
453 const gchar *end;
454
455 ptr = anjuta_token_get_string (pos);
456 end = ptr + anjuta_token_get_length (pos);
457
458 for (; ptr != end; ptr++)
459 {
460 if (*ptr == '\n')
461 {
462 /* New line */
463 loc.line++;
464 loc.column = 1;
465 }
466 else
467 {
468 loc.column++;
469 }
470
471 if (ptr == target)
472 {
473 if (location != NULL)
474 {
475 location->filename = file->file == NULL ? NULL : g_file_get_parse_name (file->file);
476 location->line = loc.line;
477 location->column = loc.column;
478 }
479
480 return TRUE;
481 }
482 }
483 }
484 }
485
486 return FALSE;
487 }
488
489 GFile*
anjuta_token_file_get_file(AnjutaTokenFile * file)490 anjuta_token_file_get_file (AnjutaTokenFile *file)
491 {
492 return file->file;
493 }
494
495 AnjutaToken*
anjuta_token_file_get_content(AnjutaTokenFile * file)496 anjuta_token_file_get_content (AnjutaTokenFile *file)
497 {
498 if (file->content == NULL)
499 {
500 anjuta_token_file_load (file, NULL);
501 }
502
503 return file->content;
504 }
505
506 gboolean
anjuta_token_file_is_dirty(AnjutaTokenFile * file)507 anjuta_token_file_is_dirty (AnjutaTokenFile *file)
508 {
509 return file->dirty;
510 }
511
512 /* GObject functions
513 *---------------------------------------------------------------------------*/
514
515 /* dispose is the first destruction step. It is used to unref object created
516 * with instance_init in order to break reference counting cycles. This
517 * function could be called several times. All function should still work
518 * after this call. It has to called its parents.*/
519
520 static void
anjuta_token_file_dispose(GObject * object)521 anjuta_token_file_dispose (GObject *object)
522 {
523 AnjutaTokenFile *file = ANJUTA_TOKEN_FILE (object);
524
525 anjuta_token_file_unload (file);
526
527 if (file->file) g_object_unref (file->file);
528 file->file = NULL;
529
530 G_OBJECT_CLASS (parent_class)->dispose (object);
531 }
532
533 /* instance_init is the constructor. All functions should work after this
534 * call. */
535
536 static void
anjuta_token_file_instance_init(AnjutaTokenFile * file)537 anjuta_token_file_instance_init (AnjutaTokenFile *file)
538 {
539 file->file = NULL;
540 file->content = NULL;
541 file->save = NULL;
542 }
543
544 /* class_init intialize the class itself not the instance */
545
546 static void
anjuta_token_file_class_init(AnjutaTokenFileClass * klass)547 anjuta_token_file_class_init (AnjutaTokenFileClass * klass)
548 {
549 GObjectClass *gobject_class;
550
551 g_return_if_fail (klass != NULL);
552
553 parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass));
554 gobject_class = G_OBJECT_CLASS (klass);
555
556 gobject_class->dispose = anjuta_token_file_dispose;
557 }
558
559 GType
anjuta_token_file_get_type(void)560 anjuta_token_file_get_type (void)
561 {
562 static GType type = 0;
563
564 if (!type)
565 {
566 static const GTypeInfo type_info =
567 {
568 sizeof (AnjutaTokenFileClass),
569 (GBaseInitFunc) NULL,
570 (GBaseFinalizeFunc) NULL,
571 (GClassInitFunc) anjuta_token_file_class_init,
572 (GClassFinalizeFunc) NULL,
573 NULL, /* class_data */
574 sizeof (AnjutaTokenFile),
575 0, /* n_preallocs */
576 (GInstanceInitFunc) anjuta_token_file_instance_init,
577 NULL /* value_table */
578 };
579
580 type = g_type_register_static (G_TYPE_OBJECT,
581 "AnjutaTokenFile", &type_info, 0);
582 }
583
584 return type;
585 }
586
587
588 /* Constructor & Destructor
589 *---------------------------------------------------------------------------*/
590
591 AnjutaTokenFile *
anjuta_token_file_new(GFile * gfile)592 anjuta_token_file_new (GFile *gfile)
593 {
594 AnjutaTokenFile *file = g_object_new (ANJUTA_TOKEN_FILE_TYPE, NULL);
595
596 if (gfile) file->file = g_object_ref (gfile);
597 file->dirty = gfile != NULL;
598
599 return file;
600 };
601
602 void
anjuta_token_file_free(AnjutaTokenFile * tfile)603 anjuta_token_file_free (AnjutaTokenFile *tfile)
604 {
605 g_object_unref (tfile);
606 }
607