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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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-document.h>
50 #include <libanjuta/interfaces/ianjuta-file.h>
51 #include <libanjuta/interfaces/ianjuta-editor-selection.h>
52 #include <libanjuta/interfaces/ianjuta-project-manager.h>
58 #include "Scintilla.h"
60 #include "ScintillaWidget.h"
63 #include "search-replace_backend.h"
64 #include "tm_tagmanager.h"
66 /* Information about a matched substring */
67 typedef struct _MatchSubStr
73 static SearchReplace
*sr
= NULL
;
75 void clear_search_replace_instance(void);
79 pcre_info_free (PcreInfo
*re
)
86 (*pcre_free
)(re
->extra
);
94 pcre_info_new (SearchExpression
*s
)
102 g_return_val_if_fail(s
&& s
->search_str
, NULL
);
103 re
= g_new0(PcreInfo
, 1);
105 options
|= PCRE_CASELESS
;
107 options
|= PCRE_UNGREEDY
;
108 re
->re
= pcre_compile(s
->search_str
, options
, &err
, &err_offset
, NULL
);
111 /* Compile failed - check error message */
112 g_warning("Regex compile failed! %s at position %d", err
, err_offset
);
116 re
->extra
= pcre_study(re
->re
, 0, &err
);
117 status
= pcre_fullinfo(re
->re
, re
->extra
, PCRE_INFO_CAPTURECOUNT
118 , &(re
->ovec_count
));
119 re
->ovector
= g_new0(int, 3 *(re
->ovec_count
+ 1));
124 static void match_substr_free(MatchSubStr
*ms
)
132 match_info_free (MatchInfo
*mi
)
139 for (tmp
= mi
->subs
; tmp
; tmp
= g_list_next(tmp
))
140 match_substr_free((MatchSubStr
*) tmp
->data
);
141 g_list_free(mi
->subs
);
149 file_buffer_free (FileBuffer
*fb
)
158 g_list_free(fb
->lines
);
163 /* Create a file buffer structure from a TextEditor structure */
165 file_buffer_new_from_te (IAnjutaEditor
*te
)
170 g_return_val_if_fail(te
, NULL
);
171 fb
= g_new0(FileBuffer
, 1);
172 fb
->type
= FB_EDITOR
;
175 uri
= ianjuta_file_get_uri(IANJUTA_FILE(te
), NULL
);
178 fb
->path
= tm_get_real_path(uri
);
181 fb
->len
= ianjuta_editor_get_length(te
, NULL
);
182 fb
->buf
= ianjuta_editor_get_text(fb
->te
, 0, fb
->len
, NULL
);
183 fb
->pos
= ianjuta_editor_get_position(fb
->te
, NULL
);
184 fb
->line
= ianjuta_editor_get_lineno(fb
->te
, NULL
);
190 file_buffer_new_from_path (const char *path
, const char *buf
, int len
, int pos
)
194 IAnjutaDocument
* doc
;
199 g_return_val_if_fail(path
, NULL
);
200 real_path
= tm_get_real_path(path
);
202 /* There might be an already open TextEditor with this path */
203 doc
= ianjuta_document_manager_find_document_with_path (sr
->docman
,
205 if (doc
&& IANJUTA_IS_EDITOR (doc
))
207 te
= IANJUTA_EDITOR (doc
);
209 return file_buffer_new_from_te(te
);
211 fb
= g_new0(FileBuffer
, 1);
213 fb
->path
= real_path
;
214 fb
->name
= strrchr(path
, '/');
221 fb
->buf
= g_new(char, len
+ 1);
222 memcpy(fb
->buf
, buf
, len
);
230 if ((0 == stat(fb
->path
, &s
)) && (S_ISREG(s
.st_mode
)))
232 if ((fb
->len
= s
.st_size
) < 0) return NULL
;
233 fb
->buf
= g_new(char, s
.st_size
+ 1);
235 int total_bytes
= 0, bytes_read
, fd
;
236 if (0 > (fd
= open(fb
->path
, O_RDONLY
)))
239 file_buffer_free(fb
);
242 while (total_bytes
< s
.st_size
)
244 if (0 > (bytes_read
= read(fd
, fb
->buf
+ total_bytes
245 , s
.st_size
- total_bytes
)))
249 file_buffer_free(fb
);
252 total_bytes
+= bytes_read
;
255 fb
->buf
[fb
->len
] = '\0';
259 if (pos
<= 0 || pos
> fb
->len
)
269 /* First line starts at column 0 */
270 fb
->lines
= g_list_prepend(fb
->lines
, GINT_TO_POINTER(0));
272 for (i
=0; i
< fb
->len
; ++i
)
274 if ('\n' == fb
->buf
[i
] && '\0' != fb
->buf
[i
+1])
276 fb
->lines
= g_list_prepend(fb
->lines
, GINT_TO_POINTER(i
+ 1));
277 if (0 == fb
->line
&& fb
->pos
> i
)
282 fb
->lines
= g_list_reverse(fb
->lines
);
287 file_buffer_line_from_pos(FileBuffer
*fb
, int pos
)
291 g_return_val_if_fail(fb
&& pos
>= 0, 1);
292 if (FB_FILE
== fb
->type
)
294 for (tmp
= fb
->lines
; tmp
; tmp
= g_list_next(tmp
))
296 if (pos
< GPOINTER_TO_INT(tmp
->data
))
302 else if (FB_EDITOR
== fb
->type
)
304 return ianjuta_editor_get_line_from_position(fb
->te
, pos
, NULL
);
311 file_match_line_from_pos(FileBuffer
*fb
, int pos
)
315 g_return_val_if_fail(fb
&& pos
>= 0, NULL
);
317 for (i
= pos
+1; ((fb
->buf
[i
] != '\n') && (fb
->buf
[i
] != '\0')); i
++, length
++);
318 for (i
= pos
-1; (fb
->buf
[i
] != '\n') && (i
>= 0); i
--, length
++);
320 return g_strndup (fb
->buf
+ i
+ 1, length
);
323 /* Generate a list of files to search in. Call with start = TRUE and
324 ** top_dir = sf->top_dir. This is used when the search range is specified as
327 create_search_files_list(SearchFiles
*sf
, const char *top_dir
)
332 g_return_val_if_fail(sf
&& top_dir
, NULL
);
333 entry
= tm_file_entry_new(top_dir
, NULL
, sf
->recurse
, sf
->match_files
334 , sf
->ignore_files
, sf
->match_dirs
, sf
->ignore_dirs
335 , sf
->ignore_hidden_files
, sf
->ignore_hidden_dirs
);
338 files
= tm_file_entry_list(entry
, NULL
);
339 tm_file_entry_free(entry
);
343 /* Get a list of all project files */
345 get_project_file_list(void)
349 gchar
*project_root_uri
= NULL
;
351 anjuta_shell_get (ANJUTA_PLUGIN(sr
->docman
)->shell
,
352 "project_root_uri", G_TYPE_STRING
,
353 &project_root_uri
, NULL
);
355 if (project_root_uri
)
357 IAnjutaProjectManager
* prjman
;
358 prjman
= anjuta_shell_get_interface(ANJUTA_PLUGIN(sr
->docman
)->shell
,
359 IAnjutaProjectManager
, NULL
);
361 list
= ianjuta_project_manager_get_elements (prjman
,
362 IANJUTA_PROJECT_MANAGER_SOURCE
,
374 uri
= (const gchar
*)node
->data
;
375 file_path
= gnome_vfs_get_local_path_from_uri (uri
);
377 files
= g_list_prepend (files
, file_path
);
378 node
= g_list_next (node
);
380 files
= g_list_reverse (files
);
384 g_free (project_root_uri
);
392 return (isalnum(c
) || '_' == c
);
396 extra_match (FileBuffer
*fb
, SearchExpression
*s
, gint match_len
)
400 b
= fb
->buf
[fb
->pos
-1];
401 e
= fb
->buf
[fb
->pos
+match_len
];
404 if ((fb
->pos
== 0 || b
== '\n' || b
== '\r') &&
405 (e
== '\0' || e
== '\n' || e
== '\r'))
409 else if (s
->whole_word
)
410 if ((fb
->pos
==0 || !isawordchar(b
)) &&
411 (e
=='\0' || !isawordchar(e
)))
415 else if (s
->word_start
)
416 if (fb
->pos
==0 || !isawordchar(b
))
424 /* Returns the next match in the passed buffer. The search expression should
425 be pre-compiled. The returned pointer should be freed with match_info_free()
426 when no longer required. */
428 get_next_match(FileBuffer
*fb
, SearchDirection direction
, SearchExpression
*s
)
430 MatchInfo
*mi
= NULL
;
432 g_return_val_if_fail(fb
&& s
, NULL
);
436 /* Regular expression match */
437 int options
= PCRE_NOTEMPTY
;
441 if (NULL
== (s
->re
= pcre_info_new(s
)))
444 status
= pcre_exec (s
->re
->re
, s
->re
->extra
, fb
->buf
, fb
->len
, fb
->pos
,
445 options
, s
->re
->ovector
, 3 * (s
->re
->ovec_count
+ 1));
448 /* ovector too small - this should never happen ! */
449 g_warning("BUG ! ovector found to be too small");
452 else if (0 > status
&& status
!= PCRE_ERROR_NOMATCH
)
454 /* match error - again, this should never happen */
455 g_warning("PCRE Match error");
458 else if (PCRE_ERROR_NOMATCH
!= status
)
460 mi
= g_new0(MatchInfo
, 1);
461 mi
->pos
= s
->re
->ovector
[0];
462 mi
->len
= s
->re
->ovector
[1] - s
->re
->ovector
[0];
463 mi
->line
= file_buffer_line_from_pos(fb
, mi
->pos
);
464 if (status
> 1) /* Captured subexpressions */
468 for (i
=1; i
< status
; ++i
)
470 ms
= g_new0(MatchSubStr
, 1);
471 ms
->start
= s
->re
->ovector
[i
* 2];
472 ms
->len
= s
->re
->ovector
[i
* 2 + 1] - ms
->start
;
473 mi
->subs
= g_list_prepend(mi
->subs
, ms
);
475 mi
->subs
= g_list_reverse(mi
->subs
);
477 fb
->pos
= s
->re
->ovector
[1];
482 /* Simple string search - this needs to be performance-tuned */
487 match_len
= strlen (s
->search_str
);
492 if (SD_BACKWARD
== direction
)
494 /* Backward matching. */
497 /* FIXME support encodings with > 1 byte per char */
498 lc
= tolower (s
->search_str
[0]);
499 for (; fb
->pos
!= -1; --fb
->pos
)
501 if (lc
== tolower(fb
->buf
[fb
->pos
]))
503 if (0 == g_strncasecmp(s
->search_str
, fb
->buf
+ fb
->pos
,
504 match_len
) && extra_match (fb
, s
, match_len
))
514 for (; fb
->pos
!= -1; --fb
->pos
)
516 if (s
->search_str
[0] == fb
->buf
[fb
->pos
])
518 if (0 == strncmp(s
->search_str
, fb
->buf
+ fb
->pos
,
519 match_len
) && extra_match (fb
, s
, match_len
))
533 /* FIXME support encodings with > 1 byte per char */
534 lc
= tolower (s
->search_str
[0]);
535 for (; fb
->pos
< fb
->len
; ++fb
->pos
)
537 if (lc
== tolower(fb
->buf
[fb
->pos
]))
539 if (0 == g_strncasecmp(s
->search_str
, fb
->buf
+ fb
->pos
,
540 match_len
) && extra_match (fb
, s
, match_len
))
550 for (; fb
->pos
< fb
->len
; ++fb
->pos
)
552 if (s
->search_str
[0] == fb
->buf
[fb
->pos
])
554 if (0 == strncmp(s
->search_str
, fb
->buf
+ fb
->pos
,
555 match_len
) && extra_match (fb
, s
, match_len
))
566 mi
= g_new0 (MatchInfo
, 1); //better to abort than silently fail to report match ?
567 // mi = g_try_new0 (MatchInfo, 1);
572 mi
->line
= file_buffer_line_from_pos (fb
, fb
->pos
);
575 // WARN USER ABOUT MEMORY ERROR
576 if (direction
== SD_BACKWARD
)
577 fb
->pos
-= match_len
;
579 fb
->pos
+= match_len
;
585 /* Create list of search entries */
587 create_search_entries (Search
*s
)
589 GList
*entries
= NULL
;
592 IAnjutaDocument
*doc
;
597 switch (s
->range
.type
)
600 doc
= ianjuta_document_manager_get_current_document (sr
->docman
, NULL
);
601 if (doc
&& IANJUTA_IS_EDITOR (doc
))
603 se
= g_new0 (SearchEntry
, 1);
604 se
->type
= SE_BUFFER
;
605 se
->te
= IANJUTA_EDITOR (doc
);
606 se
->direction
= s
->range
.direction
;
607 if (SD_BEGINNING
== se
->direction
)
611 se
->direction
= SD_FORWARD
;
615 /* forward-search from after beginning of selection, if any
616 backwards-search from before beginning of selection, if any
617 treat -ve positions except -1 as high +ve */
618 selstart
= ianjuta_editor_selection_get_start
619 (IANJUTA_EDITOR_SELECTION (se
->te
), NULL
);
622 if (se
->direction
== SD_BACKWARD
)
624 se
->start_pos
= (selstart
!= 0) ?
625 selstart
- 1 : selstart
;
630 selstart
< ianjuta_editor_get_length (IANJUTA_EDITOR (se
->te
), NULL
)) ?
631 selstart
+ 1 : selstart
;
634 se
->start_pos
= ianjuta_editor_get_position (se
->te
, NULL
);
636 se
->end_pos
= -1; /* not actually used when backward searching */
638 entries
= g_list_prepend(entries
, se
);
644 doc
= ianjuta_document_manager_get_current_document (sr
->docman
, NULL
);
645 if (doc
&& IANJUTA_IS_EDITOR (doc
))
649 se
= g_new0 (SearchEntry
, 1);
650 se
->type
= SE_BUFFER
;
651 se
->te
= IANJUTA_EDITOR (doc
);
652 se
->direction
= s
->range
.direction
;
653 if (se
->direction
== SD_BEGINNING
)
654 se
->direction
= SD_FORWARD
;
656 if (s
->range
.type
== SR_SELECTION
)
658 selstart
= selend
= 0; /* warning prevention only */
663 ianjuta_editor_selection_get_end (IANJUTA_EDITOR_SELECTION (se
->te
), NULL
);
666 if (s
->range
.type
== SR_BLOCK
)
667 ianjuta_editor_selection_select_block(IANJUTA_EDITOR_SELECTION (se
->te
), NULL
);
668 if (s
->range
.type
== SR_FUNCTION
)
669 ianjuta_editor_selection_select_function(IANJUTA_EDITOR_SELECTION (se
->te
), NULL
);
670 se
->start_pos
= ianjuta_editor_selection_get_start(IANJUTA_EDITOR_SELECTION (se
->te
), NULL
);
671 se
->end_pos
= ianjuta_editor_selection_get_end(IANJUTA_EDITOR_SELECTION (se
->te
), NULL
);
673 if (se
->direction
== SD_BACKWARD
)
675 tmp_pos
= se
->start_pos
;
676 se
->start_pos
= se
->end_pos
;
677 se
->end_pos
= tmp_pos
;
679 entries
= g_list_prepend (entries
, se
);
680 if (s
->range
.type
!= SR_SELECTION
)
683 backward
= (se
->direction
== SD_BACKWARD
);
684 ianjuta_editor_selection_set(IANJUTA_EDITOR_SELECTION (se
->te
),
685 selstart
, selend
, backward
,
690 case SR_OPEN_BUFFERS
:
691 editors
= ianjuta_document_manager_get_doc_widgets (sr
->docman
, NULL
);
692 for (tmp
= editors
; tmp
; tmp
= g_list_next(tmp
))
694 if (IANJUTA_IS_EDITOR (tmp
->data
))
696 se
= g_new0 (SearchEntry
, 1);
697 se
->type
= SE_BUFFER
;
698 se
->te
= IANJUTA_EDITOR (tmp
->data
);
699 se
->direction
= SD_FORWARD
;
702 entries
= g_list_prepend(entries
, se
);
705 entries
= g_list_reverse(entries
);
706 g_list_free (editors
);
713 gchar
*dir_uri
= NULL
;
715 anjuta_shell_get (ANJUTA_PLUGIN(sr
->docman
)->shell
,
716 "project_root_uri", G_TYPE_STRING
,
718 // FIXME : Replace Standard UNIX IO functions by gnome-vfs
720 dir
= gnome_vfs_get_local_path_from_uri(dir_uri
);
724 if (SR_PROJECT
== s
->range
.type
)
725 s
->range
.type
= SR_FILES
;
726 dir
= g_get_current_dir();
729 if (SR_FILES
== s
->range
.type
)
730 files
= create_search_files_list(&(s
->range
.files
), dir
);
731 else /* if (SR_PROJECT == s->range.type) */
732 files
= get_project_file_list();
736 for (tmp
= files
; tmp
; tmp
= g_list_next(tmp
))
738 se
= g_new0(SearchEntry
, 1);
740 se
->path
= (char *) tmp
->data
;
741 se
->direction
= SD_FORWARD
;
745 entries
= g_list_prepend(entries
, se
);
748 entries
= g_list_reverse(entries
);
759 regex_backref (MatchInfo
*mi
, FileBuffer
*fb
)
761 #define REGX_BUFSIZE 1024
767 gint backref
[10] [2]; /* backref [0][2] unused */
768 gchar buf
[REGX_BUFSIZE
+ 4]; /* usable space + word-sized space for trailing 0 */
772 /* Extract back references */
774 while (tmp
&& i
< 10)
776 backref
[i
] [0] = ((MatchSubStr
*)tmp
->data
)->start
;
777 backref
[i
] [1] = ((MatchSubStr
*)tmp
->data
)->len
;
778 tmp
= g_list_next(tmp
);
782 plen
= strlen (sr
->replace
.repl_str
);
783 for(i
=0, j
=0; i
< plen
&& j
< REGX_BUFSIZE
; i
++)
785 if (sr
->replace
.repl_str
[i
] == '\\')
788 if (sr
->replace
.repl_str
[i
] > '0' && sr
->replace
.repl_str
[i
] <= '9')
790 i_backref
= sr
->replace
.repl_str
[i
] - '0';
791 if (i_backref
< nb_backref
)
793 start
= backref
[i_backref
] [0];
794 len
= backref
[i_backref
] [1];
795 for (k
=0; k
< len
&& j
< REGX_BUFSIZE
; k
++)
796 buf
[j
++] = fb
->buf
[start
+ k
];
801 buf
[j
++] = sr
->replace
.repl_str
[i
];
805 return g_strdup (buf
);
808 #define FREE_FN(fn, v) if (v) { fn(v); v = NULL; }
811 clear_search_replace_instance(void)
813 g_free (sr
->search
.expr
.search_str
);
814 g_free (sr
->search
.expr
.re
);
815 FREE_FN(pcre_info_free
, sr
->search
.expr
.re
);
816 if (SR_FILES
== sr
->search
.range
.type
)
818 FREE_FN(anjuta_util_glist_strings_free
, sr
->search
.range
.files
.match_files
);
819 FREE_FN(anjuta_util_glist_strings_free
, sr
->search
.range
.files
.ignore_files
);
820 FREE_FN(anjuta_util_glist_strings_free
, sr
->search
.range
.files
.match_dirs
);
821 FREE_FN(anjuta_util_glist_strings_free
, sr
->search
.range
.files
.ignore_dirs
);
823 FREE_FN(anjuta_util_glist_strings_free
, sr
->search
.expr_history
);
824 g_free (sr
->replace
.repl_str
);
825 FREE_FN(anjuta_util_glist_strings_free
, sr
->replace
.expr_history
);
831 FREE_FN(pcre_info_free
, sr
->search
.expr
.re
);
835 create_search_replace_instance(IAnjutaDocumentManager
*docman
)
837 if (NULL
== sr
) /* Create a new SearchReplace instance */
838 sr
= g_new0(SearchReplace
, 1);
840 clear_search_replace_instance ();