Updated Spanish translation
[anjuta-git-plugin.git] / plugins / search / search-replace_backend.c
blob654f28d6d569912f6b21eb4a1ea571b81a06ddb8
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
3 /*
4 ** search-replace_backend.c: Generic Search and Replace
5 ** Author: Biswapesh Chattopadhyay
6 */
8 /*
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Library General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <ctype.h>
33 #include <string.h>
34 #include <dirent.h>
35 #include <fnmatch.h>
36 #include <fcntl.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
40 #include <gnome.h>
41 #include <glade/glade.h>
42 #include <libgnomevfs/gnome-vfs.h>
44 #include <libanjuta/anjuta-utils.h>
45 #include <libanjuta/anjuta-shell.h>
46 #include <libanjuta/anjuta-plugin.h>
47 #include <libanjuta/anjuta-debug.h>
48 #include <libanjuta/interfaces/ianjuta-editor.h>
49 #include <libanjuta/interfaces/ianjuta-file.h>
50 #include <libanjuta/interfaces/ianjuta-editor-selection.h>
51 #include <libanjuta/interfaces/ianjuta-project-manager.h>
54 #define GTK
55 #undef PLAT_GTK
56 #define PLAT_GTK 1
57 #include "Scintilla.h"
58 #include "SciLexer.h"
59 #include "ScintillaWidget.h"
62 #include "search-replace_backend.h"
63 #include "tm_tagmanager.h"
65 /* Information about a matched substring */
66 typedef struct _MatchSubStr
68 long start;
69 long len;
70 } MatchSubStr;
72 static SearchReplace *sr = NULL;
74 void clear_search_replace_instance(void);
77 static void
78 pcre_info_free (PcreInfo *re)
80 if (re)
82 if (re->re)
83 (*pcre_free)(re->re);
84 if (re->extra)
85 (*pcre_free)(re->extra);
86 if (re->ovector)
87 g_free(re->ovector);
88 g_free(re);
92 static PcreInfo *
93 pcre_info_new (SearchExpression *s)
95 PcreInfo *re;
96 int options = 0;
97 const char *err;
98 int err_offset;
99 int status;
101 g_return_val_if_fail(s && s->search_str, NULL);
102 re = g_new0(PcreInfo, 1);
103 if (s->ignore_case)
104 options |= PCRE_CASELESS;
105 if (!s->greedy)
106 options |= PCRE_UNGREEDY;
107 re->re = pcre_compile(s->search_str, options, &err, &err_offset, NULL);
108 if (NULL == re->re)
110 /* Compile failed - check error message */
111 g_warning("Regex compile failed! %s at position %d", err, err_offset);
112 pcre_info_free(re);
113 return NULL;
115 re->extra = pcre_study(re->re, 0, &err);
116 status = pcre_fullinfo(re->re, re->extra, PCRE_INFO_CAPTURECOUNT
117 , &(re->ovec_count));
118 re->ovector = g_new0(int, 3 *(re->ovec_count + 1));
119 return re;
123 static void match_substr_free(MatchSubStr *ms)
125 if (ms)
126 g_free(ms);
130 void
131 match_info_free (MatchInfo *mi)
133 if (mi)
135 if (mi->subs)
137 GList *tmp;
138 for (tmp = mi->subs; tmp; tmp = g_list_next(tmp))
139 match_substr_free((MatchSubStr *) tmp->data);
140 g_list_free(mi->subs);
142 g_free(mi);
147 void
148 file_buffer_free (FileBuffer *fb)
150 if (fb)
152 if (fb->path)
153 g_free(fb->path);
154 if (fb->buf)
155 g_free(fb->buf);
156 if (fb->lines)
157 g_list_free(fb->lines);
158 g_free(fb);
162 /* Create a file buffer structure from a TextEditor structure */
163 FileBuffer *
164 file_buffer_new_from_te (IAnjutaEditor *te)
166 FileBuffer *fb;
167 gchar* uri;
169 g_return_val_if_fail(te, NULL);
170 fb = g_new0(FileBuffer, 1);
171 fb->type = FB_EDITOR;
172 fb->te = te;
174 uri = ianjuta_file_get_uri(IANJUTA_FILE(te), NULL);
175 if (uri)
176 fb->path = tm_get_real_path(uri);
177 fb->len = ianjuta_editor_get_length(te, NULL);
178 fb->buf = ianjuta_editor_get_text(fb->te, 0, fb->len, NULL);
179 fb->pos = ianjuta_editor_get_position(fb->te, NULL);
180 fb->line = ianjuta_editor_get_lineno(fb->te, NULL);
182 return fb;
185 FileBuffer *
186 file_buffer_new_from_path (const char *path, const char *buf, int len, int pos)
188 FileBuffer *fb;
189 IAnjutaEditor *te;
190 char *real_path;
191 int i;
192 int lineno;
194 g_return_val_if_fail(path, NULL);
195 real_path = tm_get_real_path(path);
197 /* There might be an already open TextEditor with this path */
198 te = ianjuta_document_manager_find_editor_with_path (sr->docman,
199 real_path, NULL);
200 if (te)
202 g_free(real_path);
203 return file_buffer_new_from_te(te);
205 fb = g_new0(FileBuffer, 1);
206 fb->type = FB_FILE;
207 fb->path = real_path;
208 fb->name = strrchr(path, '/');
209 if (fb->name)
210 ++ fb->name;
211 else
212 fb->name = fb->path;
213 if (buf && len > 0)
215 fb->buf = g_new(char, len + 1);
216 memcpy(fb->buf, buf, len);
217 fb->buf[len] = '\0';
218 fb->len = len;
220 else
222 struct stat s;
224 if ((0 == stat(fb->path, &s)) && (S_ISREG(s.st_mode)))
226 if ((fb->len = s.st_size) < 0) return NULL;
227 fb->buf = g_new(char, s.st_size + 1);
229 int total_bytes = 0, bytes_read, fd;
230 if (0 > (fd = open(fb->path, O_RDONLY)))
232 perror(fb->path);
233 file_buffer_free(fb);
234 return NULL;
236 while (total_bytes < s.st_size)
238 if (0 > (bytes_read = read(fd, fb->buf + total_bytes
239 , s.st_size - total_bytes)))
241 perror(fb->path);
242 close(fd);
243 file_buffer_free(fb);
244 return NULL;
246 total_bytes += bytes_read;
248 close(fd);
249 fb->buf[fb->len] = '\0';
253 if (pos <= 0 || pos > fb->len)
255 fb->pos = 0;
256 fb->line = 0;
258 else
260 fb->pos = pos;
261 fb->line = 0;
263 /* First line starts at column 0 */
264 fb->lines = g_list_prepend(fb->lines, GINT_TO_POINTER(0));
265 lineno = 0;
266 for (i=0; i < fb->len; ++i)
268 if ('\n' == fb->buf[i] && '\0' != fb->buf[i+1])
270 fb->lines = g_list_prepend(fb->lines, GINT_TO_POINTER(i + 1));
271 if (0 == fb->line && fb->pos > i)
272 fb->line = lineno;
273 ++ lineno;
276 fb->lines = g_list_reverse(fb->lines);
277 return fb;
280 static long
281 file_buffer_line_from_pos(FileBuffer *fb, int pos)
283 GList *tmp;
284 int lineno = -1;
285 g_return_val_if_fail(fb && pos >= 0, 1);
286 if (FB_FILE == fb->type)
288 for (tmp = fb->lines; tmp; tmp = g_list_next(tmp))
290 if (pos < GPOINTER_TO_INT(tmp->data))
291 return lineno;
292 ++ lineno;
294 return lineno;
296 else if (FB_EDITOR == fb->type)
298 return ianjuta_editor_get_line_from_position(fb->te, pos, NULL);
300 else
301 return -1;
304 gchar *
305 file_match_line_from_pos(FileBuffer *fb, int pos)
307 gint length=1;
308 gint i;
309 g_return_val_if_fail(fb && pos >= 0, NULL);
311 for (i= pos+1; ((fb->buf[i] != '\n') && (fb->buf[i] != '\0')); i++, length++);
312 for (i= pos-1; (fb->buf[i] != '\n') && (i >= 0); i--, length++);
314 return g_strndup (fb->buf + i + 1, length);
317 /* Generate a list of files to search in. Call with start = TRUE and
318 ** top_dir = sf->top_dir. This is used when the search range is specified as
319 SR_FILES */
320 static GList *
321 create_search_files_list(SearchFiles *sf, const char *top_dir)
323 TMFileEntry *entry;
324 GList *files;
326 g_return_val_if_fail(sf && top_dir, NULL);
327 entry = tm_file_entry_new(top_dir, NULL, sf->recurse, sf->match_files
328 , sf->ignore_files, sf->match_dirs, sf->ignore_dirs
329 , sf->ignore_hidden_files, sf->ignore_hidden_dirs);
330 if (!entry)
331 return NULL;
332 files = tm_file_entry_list(entry, NULL);
333 tm_file_entry_free(entry);
334 return files;
337 /* Get a list of all project files */
338 static GList *
339 get_project_file_list(void)
341 GList* list = NULL;
342 GList *files = NULL;
343 gchar *project_root_uri = NULL;
345 anjuta_shell_get (ANJUTA_PLUGIN(sr->docman)->shell,
346 "project_root_uri", G_TYPE_STRING,
347 &project_root_uri, NULL);
349 if (project_root_uri)
351 IAnjutaProjectManager* prjman;
352 prjman = anjuta_shell_get_interface(ANJUTA_PLUGIN(sr->docman)->shell,
353 IAnjutaProjectManager , NULL);
355 list = ianjuta_project_manager_get_elements (prjman,
356 IANJUTA_PROJECT_MANAGER_SOURCE,
357 NULL);
358 if (list)
360 const gchar *uri;
361 GList *node;
362 node = list;
364 while (node)
366 gchar *file_path;
368 uri = (const gchar *)node->data;
369 file_path = gnome_vfs_get_local_path_from_uri (uri);
370 if (file_path)
371 files = g_list_prepend (files, file_path);
372 node = g_list_next (node);
374 files = g_list_reverse (files);
375 g_list_free(list);
378 g_free (project_root_uri);
379 return files;
383 static gboolean
384 isawordchar (int c)
386 return (isalnum(c) || '_' == c);
389 static gboolean
390 extra_match (FileBuffer *fb, SearchExpression *s, gint match_len)
392 gchar b, e;
394 b = fb->buf[fb->pos-1];
395 e = fb->buf[fb->pos+match_len];
397 if (s->whole_line)
398 if ((fb->pos == 0 || b == '\n' || b == '\r') &&
399 (e == '\0' || e == '\n' || e == '\r'))
400 return TRUE;
401 else
402 return FALSE;
403 else if (s->whole_word)
404 if ((fb->pos ==0 || !isawordchar(b)) &&
405 (e=='\0' || !isawordchar(e)))
406 return TRUE;
407 else
408 return FALSE;
409 else if (s->word_start)
410 if (fb->pos ==0 || !isawordchar(b))
411 return TRUE;
412 else
413 return FALSE;
414 else
415 return TRUE;
418 /* Returns the next match in the passed buffer. The search expression should
419 ** be pre-compiled. The returned pointer should be freed with match_info_free()
420 ** when no longer required. */
421 MatchInfo *
422 get_next_match(FileBuffer *fb, SearchDirection direction, SearchExpression *s)
424 MatchInfo *mi = NULL;
426 g_return_val_if_fail(fb && s, NULL);
428 if (s->regex)
430 /* Regular expression match */
431 int options = PCRE_NOTEMPTY;
432 int status;
433 if (NULL == s->re)
435 if (NULL == (s->re = pcre_info_new(s)))
436 return NULL;
438 status = pcre_exec(s->re->re, s->re->extra, fb->buf, fb->len, fb->pos
439 , options, s->re->ovector, 3 * (s->re->ovec_count + 1));
440 if (0 == status)
442 /* ovector too small - this should never happen ! */
443 g_warning("BUG ! ovector found to be too small");
444 return NULL;
446 else if (0 > status && status != PCRE_ERROR_NOMATCH)
448 /* match error - again, this should never happen */
449 g_warning("PCRE Match error");
450 return NULL;
452 else if (PCRE_ERROR_NOMATCH != status)
454 mi = g_new0(MatchInfo, 1);
455 mi->pos = s->re->ovector[0];
456 mi->len = s->re->ovector[1] - s->re->ovector[0];
457 mi->line = file_buffer_line_from_pos(fb, mi->pos);
458 if (status > 1) /* Captured subexpressions */
460 int i;
461 MatchSubStr *ms;
462 for (i=1; i < status; ++i)
464 ms = g_new0(MatchSubStr, 1);
465 ms->start = s->re->ovector[i * 2];
466 ms->len = s->re->ovector[i * 2 + 1] - ms->start;
467 mi->subs = g_list_prepend(mi->subs, ms);
469 mi->subs = g_list_reverse(mi->subs);
471 fb->pos = s->re->ovector[1];
474 else
476 /* Simple string search - this needs to be performance-tuned */
477 int match_len = strlen(s->search_str);
479 if (match_len == 0) return mi;
481 if (SD_BACKWARD == direction)
483 /* Backward matching. */
484 fb->pos -= match_len;
485 if (fb->pos < 0)
486 fb->pos = 0;
487 if (s->ignore_case)
489 for (; fb->pos; -- fb->pos)
491 if (tolower(s->search_str[0]) == tolower(fb->buf[fb->pos]))
493 if (0 == g_strncasecmp(s->search_str, fb->buf + fb->pos
494 , match_len) && extra_match(fb, s, match_len))
496 mi = g_new0(MatchInfo, 1);
497 mi->pos = fb->pos;
498 mi->len = match_len;
499 mi->line = file_buffer_line_from_pos(fb, mi->pos);
500 return mi;
505 else
507 for (; fb->pos; -- fb->pos)
509 if (s->search_str[0] == fb->buf[fb->pos])
511 if (0 == strncmp(s->search_str, fb->buf + fb->pos
512 , match_len) && extra_match(fb, s, match_len))
514 mi = g_new0(MatchInfo, 1);
515 mi->pos = fb->pos;
516 mi->len = match_len;
517 mi->line = file_buffer_line_from_pos(fb, mi->pos);
518 return mi;
524 else
526 /* Forward match */
527 if (s->ignore_case)
529 for (; fb->pos < fb->len; ++ fb->pos)
531 if (tolower(s->search_str[0]) == tolower(fb->buf[fb->pos]))
533 if (0 == g_strncasecmp(s->search_str, fb->buf + fb->pos
534 , match_len) && extra_match(fb, s, match_len))
536 mi = g_new0(MatchInfo, 1);
537 mi->pos = fb->pos;
538 mi->len = match_len;
539 mi->line = file_buffer_line_from_pos(fb, mi->pos);
540 fb->pos += match_len;
541 return mi;
546 else
548 for (; fb->pos < fb->len; ++ fb->pos)
550 if (s->search_str[0] == fb->buf[fb->pos])
552 if (0 == strncmp(s->search_str, fb->buf + fb->pos
553 , match_len) && extra_match(fb, s, match_len))
555 mi = g_new0(MatchInfo, 1);
556 mi->pos = fb->pos;
557 mi->len = match_len;
558 mi->line = file_buffer_line_from_pos(fb, mi->pos);
559 fb->pos += match_len;
560 return mi;
567 return mi;
570 /* Create list of search entries */
571 GList
572 *create_search_entries(Search *s)
574 GList *entries = NULL;
575 GList *tmp;
576 GList *editors;
577 SearchEntry *se;
578 long selstart;
579 long tmp_pos;
581 switch(s->range.type)
583 case SR_BUFFER:
584 se = g_new0(SearchEntry, 1);
585 se->type = SE_BUFFER;
586 se->te = ianjuta_document_manager_get_current_editor (sr->docman, NULL);
587 if (se->te != NULL)
589 se->direction = s->range.direction;
590 if (SD_BEGINNING == se->direction)
592 se->start_pos = 0;
593 se->end_pos = -1;
594 se->direction = SD_FORWARD;
596 else
598 selstart = ianjuta_editor_selection_get_start (IANJUTA_EDITOR_SELECTION (se->te), NULL);
599 se->start_pos = ianjuta_editor_get_position(se->te, NULL);
600 if ((se->direction == SD_BACKWARD) && (selstart != se->start_pos))
601 se->start_pos = selstart;
602 se->end_pos = -1;
604 entries = g_list_prepend(entries, se);
606 break;
607 case SR_SELECTION:
608 case SR_BLOCK:
609 case SR_FUNCTION:
610 se = g_new0(SearchEntry, 1);
611 se->type = SE_BUFFER;
612 se->te = ianjuta_document_manager_get_current_editor (sr->docman, NULL);
613 if (se->te != NULL)
615 gint sel_start = 0, sel_end = 0;
617 if (s->range.type != SR_SELECTION)
619 sel_start =
620 sel_end = ianjuta_editor_selection_get_end (IANJUTA_EDITOR_SELECTION (se->te), NULL);
622 se->direction = s->range.direction;
623 if (s->range.type == SR_BLOCK)
624 ianjuta_editor_selection_select_block(IANJUTA_EDITOR_SELECTION (se->te), NULL);
625 if (s->range.type == SR_FUNCTION)
626 ianjuta_editor_selection_select_function(IANJUTA_EDITOR_SELECTION (se->te), NULL);
627 if (SD_BEGINNING == se->direction)
628 se->direction = SD_FORWARD;
629 se->start_pos = ianjuta_editor_selection_get_start(IANJUTA_EDITOR_SELECTION (se->te), NULL);
630 se->end_pos = ianjuta_editor_selection_get_end(IANJUTA_EDITOR_SELECTION (se->te), NULL);
632 if (se->direction == SD_BACKWARD)
634 tmp_pos = se->start_pos;
635 se->start_pos = se->end_pos;
636 se->end_pos = tmp_pos;
638 entries = g_list_prepend(entries, se);
639 if (s->range.type != SR_SELECTION)
641 gboolean backward;
642 backward = se->direction == SD_BACKWARD?TRUE:FALSE;
643 ianjuta_editor_selection_set(IANJUTA_EDITOR_SELECTION (se->te),
644 sel_start, sel_end, backward,
645 NULL);
648 break;
649 case SR_OPEN_BUFFERS:
650 editors = ianjuta_document_manager_get_editors (sr->docman, NULL);
651 for (tmp = editors; tmp; tmp = g_list_next(tmp))
653 se = g_new0(SearchEntry, 1);
654 se->type = SE_BUFFER;
655 se->te = IANJUTA_EDITOR(tmp->data);
656 se->direction = SD_FORWARD;
657 se->start_pos = 0;
658 se->end_pos = -1;
659 entries = g_list_prepend(entries, se);
661 entries = g_list_reverse(entries);
662 break;
663 case SR_FILES:
664 case SR_PROJECT:
666 GList *files = NULL;
667 gchar *dir = NULL;
668 gchar *dir_uri = NULL;
670 anjuta_shell_get (ANJUTA_PLUGIN(sr->docman)->shell,
671 "project_root_uri", G_TYPE_STRING,
672 &dir_uri, NULL);
673 // FIXME : Replace Standard UNIX IO functions by gnome-vfs
674 if (dir_uri)
675 dir = gnome_vfs_get_local_path_from_uri(dir_uri);
677 if (!dir)
679 if (SR_PROJECT == s->range.type)
680 s->range.type = SR_FILES;
681 dir = g_get_current_dir();
684 if (SR_FILES == s->range.type)
685 files = create_search_files_list(&(s->range.files), dir);
686 else /* if (SR_PROJECT == s->range.type) */
687 files = get_project_file_list();
689 if (files)
691 for (tmp = files; tmp; tmp = g_list_next(tmp))
693 se = g_new0(SearchEntry, 1);
694 se->type = SE_FILE;
695 se->path = (char *) tmp->data;
696 se->direction = SD_FORWARD;
697 se->type = SE_FILE;
698 se->start_pos = 0;
699 se->end_pos = -1;
700 entries = g_list_prepend(entries, se);
702 g_list_free(files);
703 entries = g_list_reverse(entries);
705 g_free(dir);
706 g_free(dir_uri);
707 break;
710 return entries;
713 gchar*
714 regex_backref(MatchInfo *mi, FileBuffer *fb)
716 gint i, j, k;
717 long start, len;
718 gint nb_backref;
719 gint i_backref;
720 long backref[10] [2];
721 static gchar buf[512];
722 GList *tmp;
724 i = 1;
725 /* Extract back references */
726 tmp = mi->subs;
727 while (tmp && i < 10)
729 backref[i] [0] = ((MatchSubStr*)tmp->data)->start;
730 backref[i] [1] = ((MatchSubStr*)tmp->data)->len;
731 tmp= g_list_next(tmp);
732 i++;
734 nb_backref = i;
735 for(i=0, j=0; i < strlen(sr->replace.repl_str) && j < 512; i++)
737 if (sr->replace.repl_str[i] == '\\')
739 i++;
740 if (sr->replace.repl_str[i] >= '0' && sr->replace.repl_str[i] <= '9')
742 i_backref = sr->replace.repl_str[i] - '0';
743 if (i_backref != 0 && i_backref < nb_backref)
745 start = backref[i_backref] [0];
746 len = backref[i_backref] [1];
747 for (k=0; k < len; k++)
748 buf[j++] = fb->buf[start + k];
752 else
753 buf[j++] = sr->replace.repl_str[i];
755 buf[j] = '\0';
757 return buf;
760 #define FREE_FN(fn, v) if (v) { fn(v); v = NULL; }
762 void
763 clear_search_replace_instance(void)
765 g_free (sr->search.expr.search_str);
766 g_free (sr->search.expr.re);
767 FREE_FN(pcre_info_free, sr->search.expr.re);
768 if (SR_FILES == sr->search.range.type)
770 FREE_FN(anjuta_util_glist_strings_free, sr->search.range.files.match_files);
771 FREE_FN(anjuta_util_glist_strings_free, sr->search.range.files.ignore_files);
772 FREE_FN(anjuta_util_glist_strings_free, sr->search.range.files.match_dirs);
773 FREE_FN(anjuta_util_glist_strings_free, sr->search.range.files.ignore_dirs);
775 g_free (sr->replace.repl_str);
778 void
779 clear_pcre(void)
781 FREE_FN(pcre_info_free, sr->search.expr.re);
784 SearchReplace *
785 create_search_replace_instance(IAnjutaDocumentManager *docman)
787 if (NULL == sr) /* Create a new SearchReplace instance */
788 sr = g_new0(SearchReplace, 1);
789 else
790 clear_search_replace_instance ();
791 if (docman)
792 sr->docman = docman;
793 return sr;