1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
4 ** search-replace_backend.c: Generic Search and Replace
5 ** Author: Biswapesh Chattopadhyay
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.
37 #include <sys/types.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>
57 #include "Scintilla.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
72 static SearchReplace
*sr
= NULL
;
74 void clear_search_replace_instance(void);
78 pcre_info_free (PcreInfo
*re
)
85 (*pcre_free
)(re
->extra
);
93 pcre_info_new (SearchExpression
*s
)
101 g_return_val_if_fail(s
&& s
->search_str
, NULL
);
102 re
= g_new0(PcreInfo
, 1);
104 options
|= PCRE_CASELESS
;
106 options
|= PCRE_UNGREEDY
;
107 re
->re
= pcre_compile(s
->search_str
, options
, &err
, &err_offset
, NULL
);
110 /* Compile failed - check error message */
111 g_warning("Regex compile failed! %s at position %d", err
, err_offset
);
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));
123 static void match_substr_free(MatchSubStr
*ms
)
131 match_info_free (MatchInfo
*mi
)
138 for (tmp
= mi
->subs
; tmp
; tmp
= g_list_next(tmp
))
139 match_substr_free((MatchSubStr
*) tmp
->data
);
140 g_list_free(mi
->subs
);
148 file_buffer_free (FileBuffer
*fb
)
157 g_list_free(fb
->lines
);
162 /* Create a file buffer structure from a TextEditor structure */
164 file_buffer_new_from_te (IAnjutaEditor
*te
)
169 g_return_val_if_fail(te
, NULL
);
170 fb
= g_new0(FileBuffer
, 1);
171 fb
->type
= FB_EDITOR
;
174 uri
= ianjuta_file_get_uri(IANJUTA_FILE(te
), NULL
);
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
);
186 file_buffer_new_from_path (const char *path
, const char *buf
, int len
, int pos
)
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
,
203 return file_buffer_new_from_te(te
);
205 fb
= g_new0(FileBuffer
, 1);
207 fb
->path
= real_path
;
208 fb
->name
= strrchr(path
, '/');
215 fb
->buf
= g_new(char, len
+ 1);
216 memcpy(fb
->buf
, buf
, len
);
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
)))
233 file_buffer_free(fb
);
236 while (total_bytes
< s
.st_size
)
238 if (0 > (bytes_read
= read(fd
, fb
->buf
+ total_bytes
239 , s
.st_size
- total_bytes
)))
243 file_buffer_free(fb
);
246 total_bytes
+= bytes_read
;
249 fb
->buf
[fb
->len
] = '\0';
253 if (pos
<= 0 || pos
> fb
->len
)
263 /* First line starts at column 0 */
264 fb
->lines
= g_list_prepend(fb
->lines
, GINT_TO_POINTER(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
)
276 fb
->lines
= g_list_reverse(fb
->lines
);
281 file_buffer_line_from_pos(FileBuffer
*fb
, int pos
)
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
))
296 else if (FB_EDITOR
== fb
->type
)
298 return ianjuta_editor_get_line_from_position(fb
->te
, pos
, NULL
);
305 file_match_line_from_pos(FileBuffer
*fb
, int pos
)
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
321 create_search_files_list(SearchFiles
*sf
, const char *top_dir
)
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
);
332 files
= tm_file_entry_list(entry
, NULL
);
333 tm_file_entry_free(entry
);
337 /* Get a list of all project files */
339 get_project_file_list(void)
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
,
368 uri
= (const gchar
*)node
->data
;
369 file_path
= gnome_vfs_get_local_path_from_uri (uri
);
371 files
= g_list_prepend (files
, file_path
);
372 node
= g_list_next (node
);
374 files
= g_list_reverse (files
);
378 g_free (project_root_uri
);
386 return (isalnum(c
) || '_' == c
);
390 extra_match (FileBuffer
*fb
, SearchExpression
*s
, gint match_len
)
394 b
= fb
->buf
[fb
->pos
-1];
395 e
= fb
->buf
[fb
->pos
+match_len
];
398 if ((fb
->pos
== 0 || b
== '\n' || b
== '\r') &&
399 (e
== '\0' || e
== '\n' || e
== '\r'))
403 else if (s
->whole_word
)
404 if ((fb
->pos
==0 || !isawordchar(b
)) &&
405 (e
=='\0' || !isawordchar(e
)))
409 else if (s
->word_start
)
410 if (fb
->pos
==0 || !isawordchar(b
))
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. */
422 get_next_match(FileBuffer
*fb
, SearchDirection direction
, SearchExpression
*s
)
424 MatchInfo
*mi
= NULL
;
426 g_return_val_if_fail(fb
&& s
, NULL
);
430 /* Regular expression match */
431 int options
= PCRE_NOTEMPTY
;
435 if (NULL
== (s
->re
= pcre_info_new(s
)))
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));
442 /* ovector too small - this should never happen ! */
443 g_warning("BUG ! ovector found to be too small");
446 else if (0 > status
&& status
!= PCRE_ERROR_NOMATCH
)
448 /* match error - again, this should never happen */
449 g_warning("PCRE Match error");
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 */
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];
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
;
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);
499 mi
->line
= file_buffer_line_from_pos(fb
, mi
->pos
);
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);
517 mi
->line
= file_buffer_line_from_pos(fb
, mi
->pos
);
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);
539 mi
->line
= file_buffer_line_from_pos(fb
, mi
->pos
);
540 fb
->pos
+= match_len
;
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);
558 mi
->line
= file_buffer_line_from_pos(fb
, mi
->pos
);
559 fb
->pos
+= match_len
;
570 /* Create list of search entries */
572 *create_search_entries(Search
*s
)
574 GList
*entries
= NULL
;
581 switch(s
->range
.type
)
584 se
= g_new0(SearchEntry
, 1);
585 se
->type
= SE_BUFFER
;
586 se
->te
= ianjuta_document_manager_get_current_editor (sr
->docman
, NULL
);
589 se
->direction
= s
->range
.direction
;
590 if (SD_BEGINNING
== se
->direction
)
594 se
->direction
= SD_FORWARD
;
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
;
604 entries
= g_list_prepend(entries
, se
);
610 se
= g_new0(SearchEntry
, 1);
611 se
->type
= SE_BUFFER
;
612 se
->te
= ianjuta_document_manager_get_current_editor (sr
->docman
, NULL
);
615 gint sel_start
= 0, sel_end
= 0;
617 if (s
->range
.type
!= SR_SELECTION
)
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
)
642 backward
= se
->direction
== SD_BACKWARD
?TRUE
:FALSE
;
643 ianjuta_editor_selection_set(IANJUTA_EDITOR_SELECTION (se
->te
),
644 sel_start
, sel_end
, backward
,
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
;
659 entries
= g_list_prepend(entries
, se
);
661 entries
= g_list_reverse(entries
);
668 gchar
*dir_uri
= NULL
;
670 anjuta_shell_get (ANJUTA_PLUGIN(sr
->docman
)->shell
,
671 "project_root_uri", G_TYPE_STRING
,
673 // FIXME : Replace Standard UNIX IO functions by gnome-vfs
675 dir
= gnome_vfs_get_local_path_from_uri(dir_uri
);
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();
691 for (tmp
= files
; tmp
; tmp
= g_list_next(tmp
))
693 se
= g_new0(SearchEntry
, 1);
695 se
->path
= (char *) tmp
->data
;
696 se
->direction
= SD_FORWARD
;
700 entries
= g_list_prepend(entries
, se
);
703 entries
= g_list_reverse(entries
);
714 regex_backref(MatchInfo
*mi
, FileBuffer
*fb
)
720 long backref
[10] [2];
721 static gchar buf
[512];
725 /* Extract back references */
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
);
735 for(i
=0, j
=0; i
< strlen(sr
->replace
.repl_str
) && j
< 512; i
++)
737 if (sr
->replace
.repl_str
[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
];
753 buf
[j
++] = sr
->replace
.repl_str
[i
];
760 #define FREE_FN(fn, v) if (v) { fn(v); v = NULL; }
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
);
781 FREE_FN(pcre_info_free
, sr
->search
.expr
.re
);
785 create_search_replace_instance(IAnjutaDocumentManager
*docman
)
787 if (NULL
== sr
) /* Create a new SearchReplace instance */
788 sr
= g_new0(SearchReplace
, 1);
790 clear_search_replace_instance ();