"Fossies" - the Fresh Open Source Software Archive 
Member "regexxer-0.10/src/filebuffer.cc" (6 Oct 2011, 26095 Bytes) of package /linux/privat/old/regexxer-0.10.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "filebuffer.cc" see the
Fossies "Dox" file reference documentation.
1 /*
2 * Copyright (c) 2002-2007 Daniel Elstner <daniel.kitta@gmail.com>
3 *
4 * This file is part of regexxer.
5 *
6 * regexxer is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * regexxer is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with regexxer; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include "filebuffer.h"
22 #include "filebufferundo.h"
23 #include "globalstrings.h"
24 #include "miscutils.h"
25 #include "stringutils.h"
26 #include "translation.h"
27 #include "settings.h"
28
29 #include <glib.h>
30 #include <algorithm>
31 #include <list>
32 #include <vector>
33
34 #include <config.h>
35
36 namespace
37 {
38
39 enum { PULSE_INTERVAL = 128 };
40
41 class RegexxerTags : public Gtk::TextTagTable
42 {
43 public:
44 typedef Glib::RefPtr<Gtk::TextTag> TextTagPtr;
45
46 // This is a global singleton shared by all FileBuffer instances.
47 static Glib::RefPtr<RegexxerTags> instance();
48
49 TextTagPtr error_message;
50 TextTagPtr error_title;
51 TextTagPtr match;
52 TextTagPtr current;
53
54 protected:
55 RegexxerTags();
56 virtual ~RegexxerTags();
57
58 private:
59 void on_conf_value_changed(const Glib::ustring& key);
60 };
61
62 RegexxerTags::RegexxerTags()
63 :
64 error_message (Gtk::TextTag::create("regexxer-error-message")),
65 error_title (Gtk::TextTag::create("regexxer-error-title")),
66 match (Gtk::TextTag::create("regexxer-match")),
67 current (Gtk::TextTag::create("regexxer-current-match"))
68 {
69 error_message->property_wrap_mode() = Gtk::WRAP_WORD;
70 error_message->property_justification() = Gtk::JUSTIFY_CENTER;
71 error_message->property_pixels_above_lines() = 16;
72
73 error_title->property_scale() = Pango::SCALE_X_LARGE;
74
75 add(error_message);
76 add(error_title);
77 add(match);
78 add(current);
79
80 Glib::RefPtr<Gio::Settings> settings = Regexxer::Settings::instance();
81
82 settings->signal_changed().connect(sigc::mem_fun(*this, &RegexxerTags::on_conf_value_changed));
83 std::vector<Glib::ustring> keys = settings->list_keys();
84 for (std::vector<Glib::ustring>::iterator i = keys.begin(); i != keys.end(); ++i)
85 on_conf_value_changed(*i);
86 }
87
88 RegexxerTags::~RegexxerTags()
89 {}
90
91 void RegexxerTags::on_conf_value_changed(const Glib::ustring& key)
92 {
93 using namespace Regexxer;
94
95 if (key.raw() == conf_key_match_color)
96 match->property_background() = Settings::instance()->get_string(key);
97 else if (key.raw() == conf_key_current_match_color)
98 current->property_background() = Settings::instance()->get_string(key);
99 }
100
101 // static
102 Glib::RefPtr<RegexxerTags> RegexxerTags::instance()
103 {
104 static Glib::RefPtr<RegexxerTags> global_table (new RegexxerTags());
105 return global_table;
106 }
107
108 } // anonymous namespace
109
110 namespace Regexxer
111 {
112
113 /**** Regexxer::FileBuffer::ScopedLock *************************************/
114
115 class FileBuffer::ScopedLock
116 {
117 private:
118 FileBuffer& buffer_;
119
120 ScopedLock(const FileBuffer::ScopedLock&);
121 FileBuffer::ScopedLock& operator=(const FileBuffer::ScopedLock&);
122
123 public:
124 explicit ScopedLock(FileBuffer& buffer);
125 ~ScopedLock();
126 };
127
128 FileBuffer::ScopedLock::ScopedLock(FileBuffer& buffer)
129 :
130 buffer_ (buffer)
131 {
132 g_return_if_fail(!buffer_.locked_);
133 buffer_.locked_ = true;
134 }
135
136 FileBuffer::ScopedLock::~ScopedLock()
137 {
138 g_return_if_fail(buffer_.locked_);
139 buffer_.locked_ = false;
140 }
141
142 /**** Regexxer::FileBuffer::ScopedUserAction *******************************/
143
144 class FileBuffer::ScopedUserAction
145 {
146 private:
147 FileBuffer& buffer_;
148
149 ScopedUserAction(const FileBuffer::ScopedUserAction&);
150 FileBuffer::ScopedUserAction& operator=(const FileBuffer::ScopedUserAction&);
151
152 public:
153 explicit ScopedUserAction(FileBuffer& buffer)
154 : buffer_ (buffer) { buffer_.begin_user_action(); }
155
156 ~ScopedUserAction() { buffer_.end_user_action(); }
157 };
158
159 /**** Regexxer::FileBuffer *************************************************/
160
161 FileBuffer::FileBuffer()
162 :
163 Gsv::Buffer(Glib::RefPtr<Gtk::TextTagTable>(RegexxerTags::instance())),
164 match_set_ (),
165 current_match_ (match_set_.end()),
166 user_action_stack_ (),
167 weak_undo_stack_ (),
168 match_count_ (0),
169 original_match_count_ (0),
170 stamp_modified_ (0),
171 stamp_saved_ (0),
172 cached_bound_state_ (BOUND_FIRST | BOUND_LAST),
173 match_removed_ (false),
174 locked_ (false)
175 {}
176
177 FileBuffer::~FileBuffer()
178 {}
179
180 // static
181 Glib::RefPtr<FileBuffer> FileBuffer::create()
182 {
183 return Glib::RefPtr<FileBuffer>(new FileBuffer());
184 }
185
186 // static
187 Glib::RefPtr<FileBuffer>
188 FileBuffer::create_with_error_message(const Glib::RefPtr<Gdk::Pixbuf>& pixbuf,
189 const Glib::ustring& message)
190 {
191 const Glib::RefPtr<FileBuffer> buffer (new FileBuffer());
192 const Glib::RefPtr<RegexxerTags> tagtable = RegexxerTags::instance();
193
194 iterator pend = buffer->insert_pixbuf(buffer->end(), pixbuf);
195
196 Glib::ustring title = "\302\240"; // U+00A0 NO-BREAK SPACE
197
198 title += _("Can\342\200\231t read file:");
199 title += '\n';
200
201 pend = buffer->insert_with_tag(pend, title, tagtable->error_title);
202 pend = buffer->insert(pend, message);
203
204 if (!message.empty() && !Glib::Unicode::ispunct(*message.rbegin()))
205 pend = buffer->insert(pend, ".");
206
207 buffer->apply_tag(tagtable->error_message, buffer->begin(), pend);
208 buffer->set_modified(false);
209
210 return buffer;
211 }
212
213 // static
214 void FileBuffer::pango_context_changed(const Glib::RefPtr<Pango::Context>& context)
215 {
216 const Pango::FontDescription font_description = context->get_font_description();
217
218 // This magic code calculates the height to raise the error message title,
219 // so that it's displayed approximately in line with the error icon. By
220 // default the text would appear at the bottom, and since the icon is
221 // about 48 pixels tall this looks incredibly ugly.
222
223 int font_size = font_description.get_size();
224
225 if (font_size <= 0) // urgh, fall back to some reasonable value
226 font_size = 10 * Pango::SCALE;
227
228 int icon_width = 0, icon_height = 0;
229 Gtk::IconSize::lookup(Gtk::ICON_SIZE_DIALOG, icon_width, icon_height);
230
231 g_return_if_fail(icon_height > 0); // the lookup should never fail for builtin icon sizes
232
233 const int title_size = int(Pango::SCALE_X_LARGE * font_size);
234 const int rise_height = (icon_height * Pango::SCALE - title_size) / 2;
235
236 const Glib::RefPtr<RegexxerTags> tagtable = RegexxerTags::instance();
237
238 tagtable->error_message->property_font_desc() = font_description;
239 tagtable->error_title ->property_rise() = rise_height;
240 }
241
242 bool FileBuffer::is_freeable() const
243 {
244 return (!locked_ && match_count_ == 0 && stamp_modified_ == 0 && stamp_saved_ == 0);
245 }
246
247 bool FileBuffer::in_user_action() const
248 {
249 return (user_action_stack_ != 0);
250 }
251
252 /*
253 * Apply pattern on all lines in the buffer and return the number of matches.
254 * If multiple is false then every line is matched only once, otherwise
255 * multiple matches per line will be found (like modifier /g in Perl).
256 */
257 int FileBuffer::find_matches(const Glib::RefPtr<Glib::Regex>& pattern, bool multiple,
258 const sigc::slot<void, int, const Glib::ustring&>& feedback)
259 {
260 ScopedLock lock (*this);
261
262 const Glib::RefPtr<RegexxerTags> tagtable = RegexxerTags::instance();
263
264 notify_weak_undos();
265 forget_current_match();
266 remove_tag(tagtable->match, begin(), end());
267
268 while (!match_set_.empty())
269 delete_mark((*match_set_.begin())->mark); // triggers on_mark_deleted()
270
271 g_return_val_if_fail(match_count_ == 0, 0);
272 original_match_count_ = 0;
273
274 unsigned int iteration = 0;
275
276 for (iterator line = begin(); !line.is_end(); line.forward_line())
277 {
278 iterator line_end = line;
279
280 if (!line_end.ends_line())
281 line_end.forward_to_line_end();
282
283 const Glib::ustring subject = get_slice(line, line_end);
284 int offset = 0;
285 bool last_was_empty = false;
286
287 do
288 {
289 if ((++iteration % PULSE_INTERVAL) == 0 && signal_pulse()) // emit
290 {
291 signal_match_count_changed(); // emit
292 return match_count_;
293 }
294
295 Glib::MatchInfo match_info;
296 bool is_matched =
297 pattern->match(subject, offset, match_info,
298 (last_was_empty) ? Glib::REGEX_MATCH_ANCHORED | Glib::REGEX_MATCH_NOTEMPTY
299 : static_cast<Glib::RegexMatchFlags>(0));
300 if (!is_matched)
301 {
302 if (last_was_empty && unsigned(offset) < subject.bytes())
303 {
304 const std::string::const_iterator pbegin = subject.begin().base();
305 Glib::ustring::const_iterator poffset (pbegin + offset);
306
307 offset = (++poffset).base() - pbegin; // forward one UTF-8 character
308 last_was_empty = false;
309 continue;
310 }
311 break;
312 }
313
314 ++match_count_;
315 ++original_match_count_;
316
317 std::pair<int, int> bounds;
318 match_info.fetch_pos(0, bounds.first, bounds.second);
319
320 iterator start = line;
321 iterator stop = line;
322
323 start.set_line_index(bounds.first);
324 stop .set_line_index(bounds.second);
325
326 const MatchDataPtr match (new MatchData(
327 original_match_count_, subject, match_info));
328
329 match_set_.insert(match_set_.end(), match);
330 match->install_mark(start);
331
332 apply_tag(tagtable->match, start, stop);
333
334 if (offset == 0 && feedback)
335 feedback(line.get_line(), subject);
336
337 last_was_empty = (bounds.first == bounds.second);
338 offset = bounds.second;
339 }
340 while (multiple);
341 }
342
343 signal_match_count_changed(); // emit
344 return match_count_;
345 }
346
347 int FileBuffer::get_match_count() const
348 {
349 return match_count_;
350 }
351
352 int FileBuffer::get_match_index() const
353 {
354 return (!match_removed_ && current_match_ != match_set_.end()) ? (*current_match_)->index : 0;
355 }
356
357 int FileBuffer::get_original_match_count() const
358 {
359 return original_match_count_;
360 }
361
362 /*
363 * Move to the next match in the buffer. If there is a next match
364 * its position will be returned, otherwise 0.
365 */
366 Glib::RefPtr<FileBuffer::Mark> FileBuffer::get_next_match(bool move_forward)
367 {
368 remove_tag_current();
369
370 // If we're called just after a removal, then current_match_ already points
371 // to the next match but it doesn't have the "current-match" tag set. So we
372 // prevent moving forward since this would cause one match to be skipped.
373 // Moving backward is OK though.
374
375 if (move_forward && !match_removed_)
376 {
377 if (current_match_ != match_set_.end())
378 ++current_match_;
379 else
380 current_match_ = match_set_.begin();
381 }
382
383 if (!move_forward)
384 {
385 if (current_match_ != match_set_.begin())
386 --current_match_;
387 else
388 current_match_ = match_set_.end();
389 }
390
391 // See above; we can now safely reset this flag.
392 match_removed_ = false;
393
394 apply_tag_current();
395 update_bound_state();
396
397 return (current_match_ != match_set_.end()) ? (*current_match_)->mark : Glib::RefPtr<Mark>();
398 }
399
400 /*
401 * Remove the highlight tag from the currently selected match and forget
402 * about it. The next call to get_next_match() will start over at the
403 * first respectively last match in the buffer.
404 */
405 void FileBuffer::forget_current_match()
406 {
407 remove_tag_current();
408 current_match_ = match_set_.end();
409 match_removed_ = false;
410 }
411
412 BoundState FileBuffer::get_bound_state()
413 {
414 BoundState bound = BOUND_NONE;
415
416 if (match_set_.empty())
417 {
418 bound = BOUND_FIRST | BOUND_LAST;
419 }
420 else if (current_match_ != match_set_.end())
421 {
422 if (current_match_ == match_set_.begin())
423 bound |= BOUND_FIRST;
424
425 if (!match_removed_ && current_match_ == --match_set_.end())
426 bound |= BOUND_LAST;
427 }
428
429 cached_bound_state_ = bound;
430 return bound;
431 }
432
433 /*
434 * Replace the currently selected match with substitution. References
435 * to captured substrings in substitution will be interpolated. This
436 * method indirectly triggers emission of signal_match_count_changed()
437 * and signal_preview_line_changed().
438 */
439 void FileBuffer::replace_current_match(const Glib::ustring& substitution)
440 {
441 if (!match_removed_ && current_match_ != match_set_.end())
442 {
443 ScopedUserAction action (*this);
444
445 replace_match(current_match_, substitution);
446 }
447 }
448
449 void FileBuffer::replace_all_matches(const Glib::ustring& substitution)
450 {
451 ScopedLock lock (*this);
452 ScopedUserAction action (*this);
453
454 unsigned int iteration = 0;
455
456 while (!match_set_.empty())
457 {
458 replace_match(match_set_.begin(), substitution);
459
460 if ((++iteration % PULSE_INTERVAL) == 0 && signal_pulse()) // emit
461 break;
462 }
463 }
464
465 /*
466 * Build a preview of what replace_current_match() would do to the current
467 * line if it were called with substitution as argument. References to
468 * captured substrings in substitution will be interpolated. The result is
469 * written to the output argument preview. The return value is a character
470 * offset into preview pointing to the end of the replaced text.
471 */
472 int FileBuffer::get_line_preview(const Glib::ustring& substitution, Glib::ustring& preview)
473 {
474 int position = -1;
475 Glib::ustring result;
476
477 if (!match_removed_ && current_match_ != match_set_.end())
478 {
479 const MatchDataPtr match = *current_match_;
480
481 // Get the start of the match.
482 const iterator start = match->mark->get_iter();
483
484 // Find the end of the match.
485 iterator stop = start;
486 stop.forward_chars(match->length);
487
488 // Find begin and end of the line containing the match.
489 iterator line_begin = start;
490 iterator line_end = stop;
491 find_line_bounds(line_begin, line_end);
492
493 // Construct the preview line: [line_begin,start) + substitution + [stop,line_end)
494 result = get_text(line_begin, start);
495 result += Util::substitute_references(substitution, match->subject, match->captures);
496 position = result.length();
497 result += get_text(stop, line_end);
498 }
499
500 swap(result, preview);
501 return position;
502 }
503
504 void FileBuffer::increment_stamp()
505 {
506 ++stamp_modified_;
507
508 // No need to call set_modified() since the buffer has been modified
509 // already by the action that caused the stamp to increment.
510 }
511
512 void FileBuffer::decrement_stamp()
513 {
514 g_return_if_fail(stamp_modified_ > 0);
515
516 --stamp_modified_;
517
518 set_modified(stamp_modified_ != stamp_saved_);
519 }
520
521 void FileBuffer::undo_remove_match(const MatchDataPtr& match, int offset)
522 {
523 const std::pair<MatchSet::iterator, bool> pos = match_set_.insert(match);
524 g_return_if_fail(pos.second);
525
526 const iterator start = get_iter_at_offset(offset);
527 match->install_mark(start);
528
529 if (match->length > 0)
530 {
531 const Glib::RefPtr<RegexxerTags> tagtable = RegexxerTags::instance();
532
533 iterator stop = start;
534 stop.forward_chars(match->length);
535
536 apply_tag(tagtable->match, start, stop);
537 }
538
539 if (match_removed_ && Util::next(pos.first) == current_match_)
540 {
541 current_match_ = pos.first;
542 match_removed_ = false;
543
544 apply_tag_current();
545 }
546
547 signal_preview_line_changed.queue();
548
549 ++match_count_;
550 signal_match_count_changed(); // emit
551 update_bound_state();
552 }
553
554 /*
555 * Unfortunately it turned out to be necessary to keep track of UndoActions
556 * that reference MatchData objects, since find_matches() needs some way of
557 * telling the UndoAction objects to drop their references. Omitting this
558 * causes clashes between new matches and the obsolete ones which are still
559 * assumed to be valid by the undo actions. (In short: if it doesn't crash,
560 * it's still going to leak out memory like the Titanic leaked in water.)
561 */
562 void FileBuffer::undo_add_weak(FileBufferActionRemoveMatch* ptr)
563 {
564 weak_undo_stack_.push(ptr);
565 }
566
567 void FileBuffer::undo_remove_weak(FileBufferActionRemoveMatch* ptr)
568 {
569 // Thanks to the strict LIFO semantics of UndoStack it's possible
570 // to implement the weak references as stack too, thus reducing the
571 // book-keeping overhead to a bare minimum.
572 g_return_if_fail(weak_undo_stack_.top() == ptr);
573 weak_undo_stack_.pop();
574 }
575
576 /**** Regexxer::FileBuffer -- protected ************************************/
577
578 void FileBuffer::on_insert(const FileBuffer::iterator& pos, const Glib::ustring& text, int bytes)
579 {
580 if (!text.empty())
581 {
582 if (!match_removed_ && current_match_ != match_set_.end())
583 {
584 // Test whether pos is within the current match and push
585 // signal_preview_line_changed() on the queue if true.
586
587 iterator lbegin = (*current_match_)->mark->get_iter();
588 iterator lend = lbegin;
589 find_line_bounds(lbegin, lend);
590
591 if (pos.in_range(lbegin, lend))
592 signal_preview_line_changed.queue();
593 }
594
595 const Glib::RefPtr<RegexxerTags> tagtable = RegexxerTags::instance();
596
597 // If pos is within a match then remove this match.
598 if (pos.has_tag(tagtable->match) && !is_match_start(pos))
599 {
600 iterator start = pos;
601 start.backward_to_tag_toggle(tagtable->match);
602 remove_match_at_iter(start);
603 }
604 }
605
606 if (user_action_stack_)
607 {
608 user_action_stack_->push(UndoActionPtr(
609 new FileBufferActionInsert(*this, pos.get_offset(), text)));
610 }
611
612 Gtk::TextBuffer::on_insert(pos, text, bytes);
613 }
614
615 void FileBuffer::on_erase(const FileBuffer::iterator& rbegin, const FileBuffer::iterator& rend)
616 {
617 if (!match_removed_ && current_match_ != match_set_.end())
618 {
619 // Test whether [rbegin,rend) overlaps with the current match
620 // and push signal_preview_line_changed() on the queue if true.
621
622 iterator lbegin = (*current_match_)->mark->get_iter();
623 iterator lend = lbegin;
624 find_line_bounds(lbegin, lend);
625
626 if (lbegin.in_range(rbegin, rend) || rbegin.in_range(lbegin, lend))
627 signal_preview_line_changed.queue();
628 }
629
630 const Glib::RefPtr<const Gtk::TextTag> tag_match = RegexxerTags::instance()->match;
631
632 if (!rbegin.starts_line() && rbegin.has_tag(tag_match))
633 {
634 g_return_if_fail(!rbegin.ends_tag(tag_match)); // just to be sure...
635
636 int backward_chars = 0;
637 int match_length = -1;
638 iterator pos = rbegin;
639
640 do
641 {
642 ++backward_chars;
643 --pos;
644
645 typedef std::vector< Glib::RefPtr<Mark> > MarkList;
646 const MarkList marks (const_cast<iterator&>(pos).get_marks()); // XXX
647
648 for (MarkList::const_iterator pmark = marks.begin(); pmark != marks.end(); ++pmark)
649 {
650 if (const MatchDataPtr match_data = MatchData::get_from_mark(*pmark))
651 match_length = std::max(match_length, match_data->length);
652 }
653 }
654 while (match_length < 0 && !pos.starts_line() && !pos.toggles_tag(tag_match));
655
656 if (match_length > backward_chars)
657 remove_match_at_iter(pos);
658 }
659
660 for (iterator pos = rbegin; pos != rend; ++pos)
661 remove_match_at_iter(pos);
662
663 if (user_action_stack_)
664 {
665 user_action_stack_->push(UndoActionPtr(
666 new FileBufferActionErase(*this, rbegin.get_offset(), get_slice(rbegin, rend))));
667 }
668
669 Gtk::TextBuffer::on_erase(rbegin, rend);
670 }
671
672 void FileBuffer::on_mark_deleted(const Glib::RefPtr<FileBuffer::Mark>& mark)
673 {
674 Gtk::TextBuffer::on_mark_deleted(mark);
675
676 // If a mark has been deleted we check whether it was one of our match
677 // marks and if so, remove it from the list. Deletion of the current
678 // match has to be special cased, but's also a bit faster this way since
679 // it's the most common situation and we don't need to traverse the list.
680
681 if (current_match_ != match_set_.end() && (*current_match_)->mark == mark)
682 {
683 const MatchSet::iterator pos = current_match_++;
684
685 Glib::RefPtr<FileBuffer::Mark>().swap((*pos)->mark);
686 match_set_.erase(pos);
687 match_removed_ = true;
688
689 --match_count_;
690 signal_match_count_changed(); // emit
691 update_bound_state();
692 }
693 else if (const MatchDataPtr match_data = MatchData::get_from_mark(mark))
694 {
695 const MatchSet::iterator pos = match_set_.find(match_data);
696
697 if (pos != match_set_.end())
698 {
699 Glib::RefPtr<FileBuffer::Mark>().swap((*pos)->mark);
700 match_set_.erase(pos);
701
702 --match_count_;
703 signal_match_count_changed(); // emit
704 update_bound_state();
705 }
706 }
707 }
708
709 void FileBuffer::on_apply_tag(const Glib::RefPtr<FileBuffer::Tag>& tag,
710 const FileBuffer::iterator& range_begin,
711 const FileBuffer::iterator& range_end)
712 {
713 // Ignore tags when inserting text from the clipboard, to avoid confusion
714 // caused by highlighting text as match which is not recorded as such
715 // internally. Simply checking if a user action is currently in progress
716 // does the trick.
717 if (!in_user_action())
718 Gtk::TextBuffer::on_apply_tag(tag, range_begin, range_end);
719 }
720
721 void FileBuffer::on_modified_changed()
722 {
723 if (!get_modified())
724 stamp_saved_ = stamp_modified_;
725 }
726
727 void FileBuffer::on_begin_user_action()
728 {
729 g_return_if_fail(!user_action_stack_);
730
731 user_action_stack_.reset(new UndoStack());
732 }
733
734 void FileBuffer::on_end_user_action()
735 {
736 g_return_if_fail(user_action_stack_);
737
738 UndoStackPtr undo_action;
739 swap(undo_action, user_action_stack_);
740
741 if (!undo_action->empty())
742 signal_undo_stack_push(undo_action); // emit
743 }
744
745 /**** Regexxer::FileBuffer -- private **************************************/
746
747 void FileBuffer::replace_match(MatchSet::const_iterator pos, const Glib::ustring& substitution)
748 {
749 const MatchDataPtr match = *pos;
750
751 const Glib::ustring substituted_text =
752 Util::substitute_references(substitution, match->subject, match->captures);
753
754 // Get the start of the match.
755 const iterator start = match->mark->get_iter();
756
757 if (match->length > 0)
758 {
759 // Find end of match.
760 iterator stop = start;
761 stop.forward_chars(match->length);
762
763 // Replace match with new substituted text.
764 insert(erase(start, stop), substituted_text); // triggers on_erase() and on_insert()
765 }
766 else // empty match
767 {
768 if (user_action_stack_)
769 {
770 user_action_stack_->push(UndoActionPtr(
771 new FileBufferActionRemoveMatch(*this, start.get_offset(), match)));
772 }
773
774 // Manually remove match mark and insert the new text.
775 delete_mark(match->mark); // triggers on_mark_deleted()
776
777 if (!substituted_text.empty())
778 insert(start, substituted_text); // triggers on_insert()
779 else
780 // Do a dummy insert to avoid special case of empty-by-empty replace.
781 on_insert(start, substituted_text, 0);
782 }
783 }
784
785 /*
786 * Remove the match at position start. The removal includes the match's
787 * Mark object and the tags applied to it.
788 */
789 void FileBuffer::remove_match_at_iter(const FileBuffer::iterator& start)
790 {
791 typedef std::vector< Glib::RefPtr<Mark> > MarkList;
792 const MarkList marks (const_cast<iterator&>(start).get_marks()); // XXX
793
794 for (MarkList::const_iterator pmark = marks.begin(); pmark != marks.end(); ++pmark)
795 {
796 const MatchDataPtr match = MatchData::get_from_mark(*pmark);
797
798 if (!match)
799 continue; // not a match mark
800
801 if (user_action_stack_)
802 {
803 user_action_stack_->push(UndoActionPtr(
804 new FileBufferActionRemoveMatch(*this, start.get_offset(), match)));
805 }
806
807 if (match->length > 0)
808 {
809 const Glib::RefPtr<RegexxerTags> tagtable = RegexxerTags::instance();
810
811 iterator stop = start;
812 stop.forward_chars(match->length);
813
814 remove_tag(tagtable->match, start, stop);
815
816 if (start.begins_tag(tagtable->current))
817 remove_tag(tagtable->current, start, stop);
818 }
819
820 delete_mark(*pmark); // triggers on_mark_deleted()
821 }
822 }
823
824 void FileBuffer::remove_tag_current()
825 {
826 // If we're called just after a removal, then current_match_ already points
827 // to the next match but it doesn't have the "current-match" tag set. So we
828 // skip removal of the "current-match" tag since it doesn't exist anymore.
829
830 if (!match_removed_ && current_match_ != match_set_.end())
831 {
832 const Glib::RefPtr<RegexxerTags> tagtable = RegexxerTags::instance();
833
834 // Get the start position of the current match.
835 const iterator start = (*current_match_)->mark->get_iter();
836 const int match_length = (*current_match_)->length;
837
838 if (match_length > 0)
839 {
840 // Find the end position of the current match.
841 iterator stop = start;
842 stop.forward_chars(match_length);
843
844 remove_tag(tagtable->current, start, stop);
845 }
846 else // empty match
847 {
848 (*current_match_)->mark->set_visible(false);
849 }
850
851 signal_preview_line_changed.queue();
852 }
853 }
854
855 void FileBuffer::apply_tag_current()
856 {
857 if (!match_removed_ && current_match_ != match_set_.end())
858 {
859 const Glib::RefPtr<RegexxerTags> tagtable = RegexxerTags::instance();
860
861 // Get the start position of the match.
862 const iterator start = (*current_match_)->mark->get_iter();
863 const int match_length = (*current_match_)->length;
864
865 if (match_length > 0)
866 {
867 // Find the end position of the match.
868 iterator stop = start;
869 stop.forward_chars(match_length);
870
871 apply_tag(tagtable->current, start, stop);
872 }
873 else // empty match
874 {
875 (*current_match_)->mark->set_visible(true);
876 }
877
878 place_cursor(start);
879
880 signal_preview_line_changed.queue();
881 }
882 }
883
884 // static
885 bool FileBuffer::is_match_start(const iterator& where)
886 {
887 typedef std::vector< Glib::RefPtr<Mark> > MarkList;
888 const MarkList marks (const_cast<iterator&>(where).get_marks()); // XXX
889
890 return (std::find_if(marks.begin(), marks.end(), &MatchData::is_match_mark) != marks.end());
891 }
892
893 // static
894 void FileBuffer::find_line_bounds(FileBuffer::iterator& line_begin, FileBuffer::iterator& line_end)
895 {
896 line_begin.set_line_index(0);
897
898 if (!line_end.ends_line())
899 line_end.forward_to_line_end();
900 }
901
902 void FileBuffer::update_bound_state()
903 {
904 const BoundState old_bound_state = cached_bound_state_;
905
906 if (get_bound_state() != old_bound_state)
907 signal_bound_state_changed(); // emit
908 }
909
910 void FileBuffer::notify_weak_undos()
911 {
912 while (!weak_undo_stack_.empty())
913 {
914 FileBufferActionRemoveMatch *const ptr = weak_undo_stack_.top();
915 weak_undo_stack_.pop();
916 ptr->weak_notify();
917 }
918 }
919
920 } // namespace Regexxer