language-support-js: Fixed bgo#665082 - Crash in javascript autocompletion
[anjuta.git] / libanjuta / anjuta-token-file.c
blob49ef23b50aabbb131e11357941b2bf52c7b58935
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.
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.
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/>.
20 #include "anjuta-token-file.h"
22 #include "anjuta-debug.h"
24 #include <glib-object.h>
26 #include <stdio.h>
27 #include <string.h>
29 /* Types declarations
30 *---------------------------------------------------------------------------*/
32 struct _AnjutaTokenFile
34 GObject parent;
36 GFile* file; /* Corresponding GFile */
38 AnjutaToken *content; /* Current file content */
40 AnjutaToken *save; /* List of memory block used */
42 gboolean dirty; /* Set when the file has been modified */
45 struct _AnjutaTokenFileClass
47 GObjectClass parent_class;
50 static GObjectClass *parent_class = NULL;
52 /* Helpers functions
53 *---------------------------------------------------------------------------*/
55 /* Private functions
56 *---------------------------------------------------------------------------*/
58 static AnjutaToken*
59 anjuta_token_file_find_position (AnjutaTokenFile *file, AnjutaToken *token)
61 AnjutaToken *start;
62 const gchar *pos;
63 const gchar *ptr;
64 const gchar *end;
66 if (token == NULL) return NULL;
68 if (anjuta_token_get_length (token) == 0)
70 AnjutaToken *last = anjuta_token_last (token);
72 for (; (token != NULL) && (token != last); token = anjuta_token_next (token))
74 if (anjuta_token_get_length (token) != 0) break;
77 if (anjuta_token_get_length (token) == 0) return NULL;
80 pos = anjuta_token_get_string (token);
81 for (start = file->content; start != NULL; start = anjuta_token_next (start))
83 guint len = anjuta_token_get_length (start);
85 if (len)
87 ptr = anjuta_token_get_string (start);
88 end = ptr + len;
90 if ((pos >= ptr) && (pos < end)) break;
93 if ((start != NULL) && (ptr != pos))
95 start = anjuta_token_split (start, pos - ptr);
96 start = anjuta_token_next (start);
99 return start;
102 /* Public functions
103 *---------------------------------------------------------------------------*/
105 AnjutaToken*
106 anjuta_token_file_load (AnjutaTokenFile *file, GError **error)
108 gchar *content;
109 gsize length;
111 anjuta_token_file_unload (file);
113 if (g_file_load_contents (file->file, NULL, &content, &length, NULL, error))
115 AnjutaToken *token;
117 file->save = anjuta_token_new_static (ANJUTA_TOKEN_FILE, NULL);
118 file->content = anjuta_token_new_static (ANJUTA_TOKEN_FILE, NULL);
120 token = anjuta_token_new_string_len (ANJUTA_TOKEN_FILE, content, length);
121 anjuta_token_prepend_child (file->save, token);
123 token = anjuta_token_new_static (ANJUTA_TOKEN_FILE, content);
124 anjuta_token_prepend_child (file->content, token);
125 file->dirty = FALSE;
128 return file->content;
131 gboolean
132 anjuta_token_file_unload (AnjutaTokenFile *file)
134 if (file->content != NULL) anjuta_token_free (file->content);
135 file->content = NULL;
137 if (file->save != NULL) anjuta_token_free (file->save);
138 file->save = NULL;
140 return TRUE;
143 gboolean
144 anjuta_token_file_save (AnjutaTokenFile *file, GError **error)
146 GFileOutputStream *stream;
147 gboolean ok = TRUE;
148 GError *err = NULL;
149 AnjutaToken *token;
151 stream = g_file_replace (file->file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &err);
152 if (stream == NULL)
154 if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
156 /* Perhaps parent directory is missing, try to create it */
157 GFile *parent = g_file_get_parent (file->file);
159 if (g_file_make_directory_with_parents (parent, NULL, NULL))
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;
166 else
168 g_object_unref (parent);
169 g_propagate_error (error, err);
171 return FALSE;
174 else
176 g_propagate_error (error, err);
177 return FALSE;
181 for (token = file->content; token != NULL; token = anjuta_token_next (token))
183 if (!(anjuta_token_get_flags (token) & ANJUTA_TOKEN_REMOVED) && (anjuta_token_get_length (token)))
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)
187 ok = FALSE;
188 break;
193 ok = ok && g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, NULL);
194 g_object_unref (stream);
195 file->dirty = FALSE;
197 return ok;
200 void
201 anjuta_token_file_move (AnjutaTokenFile *file, GFile *new_file)
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;
208 static void
209 remove_raw_token (AnjutaToken *token, gpointer user_data)
211 AnjutaTokenFile *file = (AnjutaTokenFile *)user_data;
213 if ((anjuta_token_get_length (token) > 0))
215 AnjutaToken *pos = anjuta_token_file_find_position (file, token);
216 guint len = anjuta_token_get_length (token);
218 if (pos != NULL)
220 while (len != 0)
222 guint flen = anjuta_token_get_length (pos);
223 if (len < flen)
225 pos = anjuta_token_split (pos, len);
226 flen = len;
228 pos = anjuta_token_free (pos);
229 len -= flen;
235 static AnjutaToken *
236 anjuta_token_file_remove_token (AnjutaTokenFile *file, AnjutaToken *token)
238 AnjutaToken *next = NULL;
240 if (token != NULL) next = anjuta_token_foreach_post_order (token, remove_raw_token, file);
242 next = anjuta_token_free (token);
243 file->dirty = TRUE;
245 return next;
249 * anjuta_token_file_update:
250 * @file: a #AnjutaTokenFile derived class object.
251 * @token: Token to update.
253 * Update the file with all changed token starting from @token. The function can
254 * return an error if the token is not in the file.
256 * Return value: TRUE is the update is done without error.
258 gboolean
259 anjuta_token_file_update (AnjutaTokenFile *file, AnjutaToken *token)
261 AnjutaToken *prev;
262 AnjutaToken *next;
263 AnjutaToken *last;
264 guint added;
265 gchar *value;
267 /* Find all token needing an update */
269 /* Find following tokens */
270 for (last = token; last != NULL; last = anjuta_token_next (last))
272 /* Get all tokens in group */
273 last = anjuta_token_last (last);
275 gint flags = anjuta_token_get_flags (last);
276 if (!(flags & (ANJUTA_TOKEN_ADDED | ANJUTA_TOKEN_REMOVED))) break;
279 /* Find first modified token */
280 for (;;)
282 gint flags = anjuta_token_get_flags (token);
283 if (flags & (ANJUTA_TOKEN_ADDED | ANJUTA_TOKEN_REMOVED))
285 /* Check previous token */
286 for (prev = token; prev != NULL; prev = anjuta_token_previous (prev))
288 gint flags = anjuta_token_get_flags (prev);
289 if (!(flags & (ANJUTA_TOKEN_ADDED | ANJUTA_TOKEN_REMOVED))) break;
290 token = prev;
293 break;
295 if (token == last)
297 /* No changed */
298 return TRUE;
300 token = anjuta_token_next (token);
303 /* Find previous token */
304 for (prev = token; prev != NULL; prev = anjuta_token_previous (prev))
306 gint flags = anjuta_token_get_flags (prev);
307 if ((anjuta_token_get_length (prev) != 0) && !(flags & (ANJUTA_TOKEN_ADDED | ANJUTA_TOKEN_REMOVED))) break;
310 /* Delete removed token and compute length of added token */
311 added = 0;
312 for (next = token; (next != NULL) && (next != last);)
314 gint flags = anjuta_token_get_flags (next);
316 if (flags & ANJUTA_TOKEN_ADDED)
318 added += anjuta_token_get_length (next);
320 next = anjuta_token_next (next);
323 /* Add new token */
324 if (added != 0)
326 AnjutaToken *add;
327 AnjutaToken *start = NULL;
329 value = g_new (gchar, added);
330 add = anjuta_token_prepend_child (file->save, anjuta_token_new_string_len (ANJUTA_TOKEN_NAME, value, added));
332 /* Find token position */
333 if (prev != NULL)
335 start = anjuta_token_file_find_position (file, prev);
336 if (start != NULL) start = anjuta_token_split (start, anjuta_token_get_length (prev));
339 /* Insert token */
340 add = anjuta_token_new_static_len (ANJUTA_TOKEN_NAME, value, added);
341 if (start == NULL)
343 anjuta_token_prepend_child (file->content, add);
345 else
347 anjuta_token_insert_after (start, add);
351 for (next = token; (next != NULL) && (next != last);)
353 gint flags = anjuta_token_get_flags (next);
355 if (flags & ANJUTA_TOKEN_ADDED)
357 guint len = anjuta_token_get_length (next);
359 if (len > 0)
361 memcpy(value, anjuta_token_get_string (next), len);
362 anjuta_token_set_string (next, value, len);
363 value += len;
365 anjuta_token_clear_flags (next, ANJUTA_TOKEN_ADDED);
367 else if (flags & ANJUTA_TOKEN_REMOVED)
369 next = anjuta_token_file_remove_token (file, next);
370 continue;
372 next = anjuta_token_next (next);
375 file->dirty = TRUE;
377 return TRUE;
381 * anjuta_token_file_get_token_position:
382 * @file: #AnjutaTokenFile object
383 * @token: token
385 * Returns the position of the token in the file. This position is a number
386 * which doesn't correspond to a line number or a character but respect the
387 * order of token in the file.
389 * Returns: The position of the token or 0 if the token is not in the file.
391 gsize
392 anjuta_token_file_get_token_position (AnjutaTokenFile *file, AnjutaToken *token)
394 AnjutaToken *content;
395 const gchar *string;
396 gsize pos = 1;
401 string = anjuta_token_get_string (token);
402 if (string != NULL) break;
404 /* token is a group or an empty token, looks for group members or
405 * following token */
406 token = anjuta_token_next_after_children (token);
407 } while (token != NULL);
409 for (content = file->content; content != NULL; content = anjuta_token_next (content))
411 const gchar *start;
412 gsize len;
414 start = anjuta_token_get_string (content);
415 len = anjuta_token_get_length (content);
417 if ((string >= start) && ((string - start) < len))
419 pos += string - start;
420 break;
422 else
424 pos += len;
428 return content == NULL ? 0 : pos;
432 gboolean
433 anjuta_token_file_get_token_location (AnjutaTokenFile *file, AnjutaTokenFileLocation *location, AnjutaToken *token)
435 AnjutaTokenFileLocation loc = {NULL, 1, 1};
436 AnjutaToken *pos;
437 const gchar *target;
439 anjuta_token_dump (token);
442 target = anjuta_token_get_string (token);
443 if (target != NULL) break;
445 /* token is a group or an empty token, looks for group members or
446 * following token */
447 token = anjuta_token_next_after_children (token);
448 } while (token != NULL);
450 for (pos = file->content; pos != NULL; pos = anjuta_token_next (pos))
452 if (!(anjuta_token_get_flags (pos) & ANJUTA_TOKEN_REMOVED) && (anjuta_token_get_length (pos) > 0))
454 const gchar *ptr;
455 const gchar *end;
457 ptr = anjuta_token_get_string (pos);
458 end = ptr + anjuta_token_get_length (pos);
460 for (; ptr != end; ptr++)
462 if (*ptr == '\n')
464 /* New line */
465 loc.line++;
466 loc.column = 1;
468 else
470 loc.column++;
473 if (ptr == target)
475 if (location != NULL)
477 location->filename = file->file == NULL ? NULL : g_file_get_parse_name (file->file);
478 location->line = loc.line;
479 location->column = loc.column;
482 return TRUE;
488 return FALSE;
491 GFile*
492 anjuta_token_file_get_file (AnjutaTokenFile *file)
494 return file->file;
497 AnjutaToken*
498 anjuta_token_file_get_content (AnjutaTokenFile *file)
500 if (file->content == NULL)
502 anjuta_token_file_load (file, NULL);
505 return file->content;
508 gboolean
509 anjuta_token_file_is_dirty (AnjutaTokenFile *file)
511 return file->dirty;
514 /* GObject functions
515 *---------------------------------------------------------------------------*/
517 /* dispose is the first destruction step. It is used to unref object created
518 * with instance_init in order to break reference counting cycles. This
519 * function could be called several times. All function should still work
520 * after this call. It has to called its parents.*/
522 static void
523 anjuta_token_file_dispose (GObject *object)
525 AnjutaTokenFile *file = ANJUTA_TOKEN_FILE (object);
527 anjuta_token_file_unload (file);
529 if (file->file) g_object_unref (file->file);
530 file->file = NULL;
532 G_OBJECT_CLASS (parent_class)->dispose (object);
535 /* instance_init is the constructor. All functions should work after this
536 * call. */
538 static void
539 anjuta_token_file_instance_init (AnjutaTokenFile *file)
541 file->file = NULL;
542 file->content = NULL;
543 file->save = NULL;
546 /* class_init intialize the class itself not the instance */
548 static void
549 anjuta_token_file_class_init (AnjutaTokenFileClass * klass)
551 GObjectClass *gobject_class;
553 g_return_if_fail (klass != NULL);
555 parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass));
556 gobject_class = G_OBJECT_CLASS (klass);
558 gobject_class->dispose = anjuta_token_file_dispose;
561 GType
562 anjuta_token_file_get_type (void)
564 static GType type = 0;
566 if (!type)
568 static const GTypeInfo type_info =
570 sizeof (AnjutaTokenFileClass),
571 (GBaseInitFunc) NULL,
572 (GBaseFinalizeFunc) NULL,
573 (GClassInitFunc) anjuta_token_file_class_init,
574 (GClassFinalizeFunc) NULL,
575 NULL, /* class_data */
576 sizeof (AnjutaTokenFile),
577 0, /* n_preallocs */
578 (GInstanceInitFunc) anjuta_token_file_instance_init,
579 NULL /* value_table */
582 type = g_type_register_static (G_TYPE_OBJECT,
583 "AnjutaTokenFile", &type_info, 0);
586 return type;
590 /* Constructor & Destructor
591 *---------------------------------------------------------------------------*/
593 AnjutaTokenFile *
594 anjuta_token_file_new (GFile *gfile)
596 AnjutaTokenFile *file = g_object_new (ANJUTA_TOKEN_FILE_TYPE, NULL);
598 if (gfile) file->file = g_object_ref (gfile);
599 file->dirty = gfile != NULL;
601 return file;
604 void
605 anjuta_token_file_free (AnjutaTokenFile *tfile)
607 g_object_unref (tfile);