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/anjuta-encodings.h>
49 #include <libanjuta/anjuta-convert.h>
50 #include <libanjuta/interfaces/ianjuta-editor.h>
51 #include <libanjuta/interfaces/ianjuta-document.h>
52 #include <libanjuta/interfaces/ianjuta-file.h>
53 #include <libanjuta/interfaces/ianjuta-editor-selection.h>
54 #include <libanjuta/interfaces/ianjuta-project-manager.h>
60 #include "Scintilla.h"
62 #include "ScintillaWidget.h"
65 #include "search-replace_backend.h"
66 #include "tm_tagmanager.h"
68 /* Information about a matched substring */
69 typedef struct _MatchSubStr
75 static SearchReplace
*sr
= NULL
;
77 void clear_search_replace_instance(void);
79 static void match_substr_free(MatchSubStr
*ms
)
86 match_info_free (MatchInfo
*mi
)
93 for (tmp
= mi
->subs
; tmp
; tmp
= g_list_next(tmp
))
94 match_substr_free((MatchSubStr
*) tmp
->data
);
95 g_list_free(mi
->subs
);
103 file_buffer_free (FileBuffer
*fb
)
114 g_list_free(fb
->lines
);
119 /* Create a file buffer structure from a TextEditor structure */
121 file_buffer_new_from_te (IAnjutaEditor
*te
)
127 g_return_val_if_fail(te
, NULL
);
128 fb
= g_new0(FileBuffer
, 1);
129 fb
->type
= FB_EDITOR
;
132 file
= ianjuta_file_get_file(IANJUTA_FILE(te
), NULL
);
133 path
= g_file_get_path (file
);
136 fb
->path
= tm_get_real_path(path
);
139 fb
->uri
= g_file_get_uri (file
);
140 fb
->len
= ianjuta_editor_get_length(te
, NULL
);
141 fb
->buf
= ianjuta_editor_get_text_all (fb
->te
, NULL
);
142 fb
->pos
= ianjuta_editor_get_offset(fb
->te
, NULL
);
143 fb
->line
= ianjuta_editor_get_lineno(fb
->te
, NULL
);
145 g_object_unref (file
);
150 /* Only use the first 500 chars for validating (yes, I feel lucky...) */
151 #define MAX_VALIDATE 500
154 file_buffer_new_from_path (const char *path
, const char *buf
, int len
, int pos
)
158 IAnjutaDocument
* doc
;
165 g_return_val_if_fail(path
, NULL
);
166 real_path
= tm_get_real_path(path
);
168 /* There might be an already open TextEditor with this path */
169 file
= g_file_new_for_path (real_path
);
170 uri
= g_file_get_uri (file
);
171 doc
= ianjuta_document_manager_find_document_with_file (sr
->docman
,
173 g_object_unref (file
);
175 if (doc
&& IANJUTA_IS_EDITOR (doc
))
177 te
= IANJUTA_EDITOR (doc
);
179 return file_buffer_new_from_te(te
);
181 fb
= g_new0(FileBuffer
, 1);
183 fb
->path
= real_path
;
185 fb
->name
= strrchr(path
, '/');
192 fb
->buf
= g_new(char, len
+ 1);
193 memcpy(fb
->buf
, buf
, len
);
201 if ((0 == stat(fb
->path
, &s
)) && (S_ISREG(s
.st_mode
)))
203 if ((fb
->len
= s
.st_size
) < 0) return NULL
;
204 fb
->buf
= g_new(char, s
.st_size
+ 1);
206 int total_bytes
= 0, bytes_read
, fd
;
207 if (0 > (fd
= open(fb
->path
, O_RDONLY
)))
210 file_buffer_free(fb
);
213 while (total_bytes
< s
.st_size
)
215 if (0 > (bytes_read
= read(fd
, fb
->buf
+ total_bytes
216 , s
.st_size
- total_bytes
)))
220 file_buffer_free(fb
);
223 total_bytes
+= bytes_read
;
226 fb
->buf
[fb
->len
] = '\0';
230 if (!g_utf8_validate (fb
->buf
, MIN(MAX_VALIDATE
, fb
->len
), NULL
))
232 const AnjutaEncoding
*encoding_used
= NULL
;
233 gchar
* converted_text
;
235 converted_text
= anjuta_convert_to_utf8 (fb
->buf
,
240 if (converted_text
== NULL
)
242 /* Last change, let's try 8859-15 */
244 anjuta_encoding_get_from_charset("ISO-8859-15");
246 converted_text
= anjuta_convert_to_utf8 (fb
->buf
,
252 if (converted_text
== NULL
)
255 file_buffer_free(fb
);
261 fb
->buf
= converted_text
;
262 fb
->len
= converted_len
;
266 if (pos
<= 0 || pos
> fb
->len
)
276 /* First line starts at column 0 */
277 fb
->lines
= g_list_prepend(fb
->lines
, GINT_TO_POINTER(0));
279 for (i
=0; i
< fb
->len
; ++i
)
281 if ('\n' == fb
->buf
[i
] && '\0' != fb
->buf
[i
+1])
283 fb
->lines
= g_list_prepend(fb
->lines
, GINT_TO_POINTER(i
+ 1));
284 if (0 == fb
->line
&& fb
->pos
> i
)
289 fb
->lines
= g_list_reverse(fb
->lines
);
294 file_buffer_line_from_pos(FileBuffer
*fb
, int pos
)
298 g_return_val_if_fail(fb
&& pos
>= 0, 1);
299 if (FB_FILE
== fb
->type
)
301 for (tmp
= fb
->lines
; tmp
; tmp
= g_list_next(tmp
))
303 if (pos
< GPOINTER_TO_INT(tmp
->data
))
309 else if (FB_EDITOR
== fb
->type
)
311 IAnjutaIterable
*position
;
312 position
= ianjuta_editor_get_position_from_offset (fb
->te
, pos
, NULL
);
313 lineno
= ianjuta_editor_get_line_from_position (fb
->te
, position
, NULL
);
314 g_object_unref (position
);
322 file_match_line_from_pos(FileBuffer
*fb
, int pos
)
326 g_return_val_if_fail(fb
&& pos
>= 0, NULL
);
328 for (i
= pos
+1; ((fb
->buf
[i
] != '\n') && (fb
->buf
[i
] != '\0')); i
++, length
++);
329 for (i
= pos
-1; (fb
->buf
[i
] != '\n') && (i
>= 0); i
--, length
++);
331 return g_strndup (fb
->buf
+ i
+ 1, length
);
334 /* Generate a list of files to search in. Call with start = TRUE and
335 ** top_dir = sf->top_dir. This is used when the search range is specified as
338 create_search_files_list(SearchFiles
*sf
, const char *top_dir
)
343 g_return_val_if_fail(sf
&& top_dir
, NULL
);
344 entry
= tm_file_entry_new(top_dir
, NULL
, sf
->recurse
, sf
->match_files
345 , sf
->ignore_files
, sf
->match_dirs
, sf
->ignore_dirs
346 , sf
->ignore_hidden_files
, sf
->ignore_hidden_dirs
);
349 files
= tm_file_entry_list(entry
, NULL
);
350 tm_file_entry_free(entry
);
354 /* Get a list of all project files */
356 get_project_file_list(void)
360 gchar
*project_root_uri
= NULL
;
362 anjuta_shell_get (ANJUTA_PLUGIN(sr
->docman
)->shell
,
363 "project_root_uri", G_TYPE_STRING
,
364 &project_root_uri
, NULL
);
366 if (project_root_uri
)
368 IAnjutaProjectManager
* prjman
;
369 prjman
= anjuta_shell_get_interface(ANJUTA_PLUGIN(sr
->docman
)->shell
,
370 IAnjutaProjectManager
, NULL
);
372 list
= ianjuta_project_manager_get_elements (prjman
,
373 IANJUTA_PROJECT_MANAGER_SOURCE
,
385 uri
= (const gchar
*)node
->data
;
386 file_path
= gnome_vfs_get_local_path_from_uri (uri
);
388 files
= g_list_prepend (files
, file_path
);
389 node
= g_list_next (node
);
391 files
= g_list_reverse (files
);
395 g_free (project_root_uri
);
401 isawordchar (gunichar c
)
403 return (g_unichar_isalnum(c
) || '_' == c
);
407 extra_match (FileBuffer
*fb
, gchar
* begin
, gchar
* end
, SearchExpression
*s
)
411 b
= g_utf8_get_char (g_utf8_prev_char (begin
));
412 e
= g_utf8_get_char (end
);
415 if ((fb
->pos
== 0 || b
== '\n' || b
== '\r') &&
416 (e
== '\0' || e
== '\n' || e
== '\r'))
420 else if (s
->whole_word
)
421 if ((fb
->pos
==0 || !isawordchar(b
)) &&
422 (e
=='\0' || !isawordchar(e
)))
426 else if (s
->word_start
)
427 if (fb
->pos
==0 || !isawordchar(b
))
435 /* Returns the next match in the passed buffer. The search expression should
436 be pre-compiled. The returned pointer should be freed with match_info_free()
437 when no longer required. */
439 get_next_match(FileBuffer
*fb
, SearchDirection direction
, SearchExpression
*s
)
441 MatchInfo
*mi
= NULL
;
443 g_return_val_if_fail(fb
&& s
, NULL
);
447 GMatchInfo
* match_info
;
448 if (s
->regex_info
== NULL
)
450 GError
* error
= NULL
;
451 GRegexCompileFlags compile_flags
= 0;
452 GRegexMatchFlags match_flags
= 0;
454 match_flags
|= G_REGEX_MATCH_NOTEMPTY
;
457 compile_flags
|= G_REGEX_CASELESS
;
461 compile_flags
|= G_REGEX_UNGREEDY
;
463 s
->regex_info
= g_regex_new (s
->search_str
, compile_flags
,
464 match_flags
, &error
);
467 anjuta_util_dialog_error (NULL
, error
->message
);
469 s
->regex_info
= NULL
;
474 g_regex_match_full (s
->regex_info
, fb
->buf
, fb
->len
,
475 g_utf8_offset_to_pointer (fb
->buf
, fb
->pos
) - fb
->buf
,
476 G_REGEX_MATCH_NOTEMPTY
, &match_info
, NULL
);
478 if (g_match_info_matches (match_info
))
483 mi
= g_new0(MatchInfo
, 1);
484 if (g_match_info_fetch_pos (match_info
, 0, &start
, &end
))
486 DEBUG_PRINT ("Regex: %d %d", start
, end
);
487 mi
->pos
= g_utf8_pointer_to_offset (fb
->buf
, fb
->buf
+ start
);
488 mi
->len
= g_utf8_pointer_to_offset (fb
->buf
, fb
->buf
+ end
) - mi
->pos
;
489 mi
->line
= file_buffer_line_from_pos(fb
, mi
->pos
);
491 for (i
= 1; i
< g_match_info_get_match_count(match_info
); i
++) /* Captured subexpressions */
494 ms
= g_new0(MatchSubStr
, 1);
495 if (g_match_info_fetch_pos (match_info
, i
, &start
, &end
))
497 ms
->start
= g_utf8_pointer_to_offset (fb
->buf
, fb
->buf
+ start
);
498 ms
->len
= g_utf8_pointer_to_offset (fb
->buf
, fb
->buf
+ end
) - ms
->start
;
500 mi
->subs
= g_list_prepend(mi
->subs
, ms
);
502 mi
->subs
= g_list_reverse(mi
->subs
);
503 fb
->pos
= g_utf8_pointer_to_offset (fb
->buf
, fb
->buf
+ end
);
508 /* Simple string search - this needs to be performance-tuned */
512 match_len
= strlen (s
->search_str
);
517 if (SD_BACKWARD
== direction
)
519 /* Backward matching. */
522 gchar
* current
= g_utf8_offset_to_pointer (fb
->buf
, fb
->pos
);
523 gint len
= g_utf8_strlen (s
->search_str
, -1);
524 gchar
* search_caseless
= g_utf8_casefold (s
->search_str
, len
);
525 for (; fb
->pos
>= len
; --fb
->pos
)
527 gchar
* current_caseless
= g_utf8_casefold (current
, len
);
528 if (g_utf8_collate (current_caseless
, search_caseless
) == 0 &&
529 extra_match (fb
, current
, current
+ strlen (search_caseless
),
533 g_free (current_caseless
);
537 current
= g_utf8_prev_char (current
);
539 g_free (search_caseless
);
543 gchar
* current
= g_utf8_offset_to_pointer (fb
->buf
, fb
->pos
);
544 gint len
= g_utf8_strlen (s
->search_str
, -1);
545 gchar
* search_key
= g_utf8_collate_key (s
->search_str
, len
);
546 for (; fb
->pos
>= len
; --fb
->pos
)
548 gchar
* current_key
= g_utf8_collate_key (current
, len
);
549 if (g_str_equal (current_key
, search_key
) &&
550 extra_match (fb
, current
, current
+ strlen (s
->search_str
),
554 g_free (current_key
);
558 current
= g_utf8_prev_char (current
);
568 gchar
* current
= g_utf8_offset_to_pointer (fb
->buf
, fb
->pos
);
569 gint len
= g_utf8_strlen (s
->search_str
, -1);
570 gchar
* search_caseless
= g_utf8_casefold (s
->search_str
, len
);
571 gint buf_len
= g_utf8_strlen (fb
->buf
, fb
->len
);
572 for (; fb
->pos
< buf_len
; ++fb
->pos
)
574 gchar
* current_caseless
= g_utf8_casefold (current
, len
);
575 if (g_utf8_collate (current_caseless
, search_caseless
) == 0 &&
576 extra_match (fb
, current
, current
+ strlen (search_caseless
),
580 g_free (current_caseless
);
584 current
= g_utf8_next_char (current
);
586 g_free (search_caseless
);
590 gchar
* current
= g_utf8_offset_to_pointer (fb
->buf
, fb
->pos
);
591 gint len
= g_utf8_strlen (s
->search_str
, -1);
592 gint buf_len
= g_utf8_strlen (fb
->buf
, fb
->len
);
593 gchar
* search_key
= g_utf8_collate_key (s
->search_str
, len
);
594 for (; fb
->pos
< buf_len
; ++fb
->pos
)
596 gchar
* current_key
= g_utf8_collate_key (current
, len
);
597 if (g_str_equal (current_key
, search_key
) &&
598 extra_match (fb
, current
, current
+ strlen (s
->search_str
),
602 g_free (current_key
);
606 current
= g_utf8_next_char (current
);
613 mi
= g_new0 (MatchInfo
, 1); //better to abort than silently fail to report match ?
614 // mi = g_try_new0 (MatchInfo, 1);
619 mi
->line
= file_buffer_line_from_pos (fb
, fb
->pos
);
622 // WARN USER ABOUT MEMORY ERROR
623 if (direction
== SD_BACKWARD
)
624 fb
->pos
-= match_len
;
626 fb
->pos
+= match_len
;
632 /* Create list of search entries */
634 create_search_entries (Search
*s
)
636 GList
*entries
= NULL
;
639 IAnjutaDocument
*doc
;
644 switch (s
->range
.type
)
647 doc
= ianjuta_document_manager_get_current_document (sr
->docman
, NULL
);
648 if (doc
&& IANJUTA_IS_EDITOR (doc
))
650 se
= g_new0 (SearchEntry
, 1);
651 se
->type
= SE_BUFFER
;
652 se
->te
= IANJUTA_EDITOR (doc
);
653 se
->direction
= s
->range
.direction
;
654 if (SD_BEGINNING
== se
->direction
)
658 se
->direction
= SD_FORWARD
;
662 IAnjutaIterable
*start
;
663 /* forward-search from after beginning of selection, if any
664 backwards-search from before beginning of selection, if any
665 treat -ve positions except -1 as high +ve */
666 start
= ianjuta_editor_selection_get_start
667 (IANJUTA_EDITOR_SELECTION (se
->te
), NULL
);
671 ianjuta_iterable_get_position (start
, NULL
);
672 if (se
->direction
== SD_BACKWARD
)
674 se
->start_pos
= (selstart
!= 0) ?
675 selstart
- 1 : selstart
;
681 selstart
< ianjuta_editor_get_length (IANJUTA_EDITOR (se
->te
), NULL
)) ?
682 selstart
+ 1 : selstart
;
684 g_object_unref (start
);
688 se
->start_pos
= ianjuta_editor_get_offset (se
->te
, NULL
);
690 se
->end_pos
= -1; /* not actually used when backward searching */
692 entries
= g_list_prepend(entries
, se
);
698 doc
= ianjuta_document_manager_get_current_document (sr
->docman
, NULL
);
699 if (doc
&& IANJUTA_IS_EDITOR (doc
))
703 se
= g_new0 (SearchEntry
, 1);
704 se
->type
= SE_BUFFER
;
705 se
->te
= IANJUTA_EDITOR (doc
);
706 se
->direction
= s
->range
.direction
;
707 if (se
->direction
== SD_BEGINNING
)
708 se
->direction
= SD_FORWARD
;
710 if (s
->range
.type
== SR_SELECTION
)
712 selstart
= selend
= 0; /* warning prevention only */
716 IAnjutaIterable
* end
=
717 ianjuta_editor_selection_get_end (IANJUTA_EDITOR_SELECTION (se
->te
), NULL
);
720 selstart
= selend
= ianjuta_iterable_get_position (end
, NULL
);
721 g_object_unref (end
);
725 selstart
= selend
= 0; /* warning prevention only */
726 g_assert ("No selection end position");
730 if (s
->range
.type
== SR_BLOCK
)
731 ianjuta_editor_selection_select_block(IANJUTA_EDITOR_SELECTION (se
->te
), NULL
);
732 if (s
->range
.type
== SR_FUNCTION
)
733 ianjuta_editor_selection_select_function(IANJUTA_EDITOR_SELECTION (se
->te
), NULL
);
735 IAnjutaIterable
*start
, *end
;
736 start
= ianjuta_editor_selection_get_start (IANJUTA_EDITOR_SELECTION (se
->te
), NULL
);
737 end
= ianjuta_editor_selection_get_end(IANJUTA_EDITOR_SELECTION (se
->te
), NULL
);
738 se
->start_pos
= ianjuta_iterable_get_position (start
, NULL
);
739 se
->end_pos
= ianjuta_iterable_get_position (end
, NULL
);
740 g_object_unref (start
);
741 g_object_unref (end
);
743 if (se
->direction
== SD_BACKWARD
)
745 tmp_pos
= se
->start_pos
;
746 se
->start_pos
= se
->end_pos
;
747 se
->end_pos
= tmp_pos
;
749 entries
= g_list_prepend (entries
, se
);
750 if (s
->range
.type
!= SR_SELECTION
)
752 IAnjutaIterable
*start
, *end
;
753 start
= ianjuta_editor_get_position_from_offset (se
->te
, selstart
, NULL
);
754 end
= ianjuta_editor_get_position_from_offset (se
->te
, selend
, NULL
);
755 ianjuta_editor_selection_set(IANJUTA_EDITOR_SELECTION (se
->te
),
757 g_object_unref (start
);
758 g_object_unref (end
);
762 case SR_OPEN_BUFFERS
:
763 editors
= ianjuta_document_manager_get_doc_widgets (sr
->docman
, NULL
);
764 for (tmp
= editors
; tmp
; tmp
= g_list_next(tmp
))
766 if (IANJUTA_IS_EDITOR (tmp
->data
))
768 se
= g_new0 (SearchEntry
, 1);
769 se
->type
= SE_BUFFER
;
770 se
->te
= IANJUTA_EDITOR (tmp
->data
);
771 se
->direction
= SD_FORWARD
;
774 entries
= g_list_prepend(entries
, se
);
777 entries
= g_list_reverse(entries
);
778 g_list_free (editors
);
785 gchar
*dir_uri
= NULL
;
787 anjuta_shell_get (ANJUTA_PLUGIN(sr
->docman
)->shell
,
788 "project_root_uri", G_TYPE_STRING
,
790 // FIXME : Replace Standard UNIX IO functions by gnome-vfs
792 dir
= gnome_vfs_get_local_path_from_uri(dir_uri
);
796 if (SR_PROJECT
== s
->range
.type
)
797 s
->range
.type
= SR_FILES
;
798 dir
= g_get_current_dir();
801 if (SR_FILES
== s
->range
.type
)
802 files
= create_search_files_list(&(s
->range
.files
), dir
);
803 else /* if (SR_PROJECT == s->range.type) */
804 files
= get_project_file_list();
808 for (tmp
= files
; tmp
; tmp
= g_list_next(tmp
))
810 se
= g_new0(SearchEntry
, 1);
812 se
->path
= (char *) tmp
->data
;
813 se
->direction
= SD_FORWARD
;
817 entries
= g_list_prepend(entries
, se
);
820 entries
= g_list_reverse(entries
);
831 regex_backref (MatchInfo
*mi
, FileBuffer
*fb
)
833 #define REGX_BUFSIZE 1024
839 gint backref
[10] [2]; /* backref [0][2] unused */
840 gchar buf
[REGX_BUFSIZE
+ 4]; /* usable space + word-sized space for trailing 0 */
844 /* Extract back references */
846 while (tmp
&& i
< 10)
848 backref
[i
] [0] = ((MatchSubStr
*)tmp
->data
)->start
;
849 backref
[i
] [1] = ((MatchSubStr
*)tmp
->data
)->len
;
850 tmp
= g_list_next(tmp
);
854 plen
= strlen (sr
->replace
.repl_str
);
855 for(i
=0, j
=0; i
< plen
&& j
< REGX_BUFSIZE
; i
++)
857 if (sr
->replace
.repl_str
[i
] == '\\')
860 if (sr
->replace
.repl_str
[i
] > '0' && sr
->replace
.repl_str
[i
] <= '9')
862 i_backref
= sr
->replace
.repl_str
[i
] - '0';
863 if (i_backref
< nb_backref
)
865 start
= backref
[i_backref
] [0];
866 len
= backref
[i_backref
] [1];
867 for (k
=0; k
< len
&& j
< REGX_BUFSIZE
; k
++)
868 buf
[j
++] = fb
->buf
[start
+ k
];
873 buf
[j
++] = sr
->replace
.repl_str
[i
];
877 return g_strdup (buf
);
880 #define FREE_FN(fn, v) if (v) { fn(v); v = NULL; }
883 clear_search_replace_instance(void)
885 g_free (sr
->search
.expr
.search_str
);
886 if (sr
->search
.expr
.regex_info
)
888 g_regex_unref (sr
->search
.expr
.regex_info
);
889 sr
->search
.expr
.regex_info
= NULL
;
891 if (SR_FILES
== sr
->search
.range
.type
)
893 FREE_FN(anjuta_util_glist_strings_free
, sr
->search
.range
.files
.match_files
);
894 FREE_FN(anjuta_util_glist_strings_free
, sr
->search
.range
.files
.ignore_files
);
895 FREE_FN(anjuta_util_glist_strings_free
, sr
->search
.range
.files
.match_dirs
);
896 FREE_FN(anjuta_util_glist_strings_free
, sr
->search
.range
.files
.ignore_dirs
);
898 FREE_FN(anjuta_util_glist_strings_free
, sr
->search
.expr_history
);
899 g_free (sr
->replace
.repl_str
);
900 FREE_FN(anjuta_util_glist_strings_free
, sr
->replace
.expr_history
);
904 create_search_replace_instance(IAnjutaDocumentManager
*docman
)
906 /* Create a new SearchReplace instance */
909 sr
= g_new0(SearchReplace
, 1);
910 sr
->search
.expr
.regex_info
= NULL
;
913 clear_search_replace_instance ();