"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