Control: tags -1 patch Please find two patches attached that allow to build with libgtksourceview-4-dev instead of libgtksourceviewmm-3.0-dev.
From: Bastian Germann <[email protected]> Date: Tue, 29 Sep 2026 08:49:07 +0200 Subject: Drop the obsolete AM_GNU_GETTEXT_VERSION from configure.ac
autoreconf fails with configure.ac:35: error: undefined or overquoted macro: AM_GNU_GETTEXT_VERSION because that macro belongs to the old gettext 0.11 interface, which current gettext releases no longer provide. aclocal reports configure.ac:18: warning: macro 'AM_GNU_GETTEXT_VERSION' not found in library and autoconf then refuses to expand it, so dh_autoreconf never gets as far as generating configure. The call is redundant in any case: the very next line, AM_GLIB_GNU_GETTEXT from glib-gettext.m4, sets up the gettext machinery itself and only AC_REQUIREs AC_PROG_CC, so removing the vestigial line leaves the build untouched. autoreconf, configure and make all succeed afterwards. --- diff --git a/configure.ac b/configure.ac index 0dcf4d8..2dfa495 100644 --- a/configure.ac +++ b/configure.ac @@ -32,7 +32,6 @@ AC_PROG_CXX IT_PROG_INTLTOOL([0.35]) AC_SUBST([GETTEXT_PACKAGE], [regexxer]) -AM_GNU_GETTEXT_VERSION([0.11]) AM_GLIB_GNU_GETTEXT PKG_CHECK_MODULES([REGEXXER_MODULES],
From: Bastian Germann <[email protected]> Date: Tue, 29 Sep 2026 08:36:25 +0200 Subject: Port from gtksourceviewmm to the GtkSourceView 4 C API gtksourceviewmm has been unmaintained for over a decade, so bind the handful of GtkSourceView entry points regexxer actually uses directly in src/sourceview.{h,cc} instead of pulling in the whole C++ wrapper library. FileBuffer keeps deriving from Gtk::TextBuffer, but is now backed by a registered GType derived from GtkSourceBuffer. The insert_text, delete_range, mark_deleted and apply_tag vfuncs dispatch the C++ bookkeeping to a copy of the iterator and then chain up to GtkTextBuffer with the caller's original raw GtkTextIter pointers. Passing copies up instead makes GTK update throwaway iterators, which silently truncated file loading to the first 4096 bytes, turned replace_match() into a no-op, and produced invalid-iterator warnings during undo. The draw-spaces preference is applied through the space drawer that GtkSourceView 4 replaced gtk_source_view_set_draw_spaces() with. The preference bitmask is kept as regexxer's own enum and is asserted to match the GtkSourceSpaceType bits, which are identical. configure.ac now requires gtksourceview-4 >= 4.0.0. --- diff --git a/Makefile.am b/Makefile.am index e027347..8856ba5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -57,6 +57,8 @@ src_regexxer_SOURCES = \ src/sharedptr.h \ src/signalutils.cc \ src/signalutils.h \ + src/sourceview.cc \ + src/sourceview.h \ src/statusline.cc \ src/statusline.h \ src/stringutils.cc \ diff --git a/configure.ac b/configure.ac index 0dcf4d8..b01d371 100644 --- a/configure.ac +++ b/configure.ac @@ -37,7 +37,7 @@ AM_GLIB_GNU_GETTEXT PKG_CHECK_MODULES([REGEXXER_MODULES], [gtkmm-3.0 >= 3.0.0 glibmm-2.4 >= 2.27.94 - gtksourceviewmm-3.0 >= 2.91.5]) + gtksourceview-4 >= 4.0.0]) DK_PKG_PATH_PROG([GDK_PIXBUF_CSOURCE], [gdk-pixbuf-2.0], [gdk-pixbuf-csource]) DK_PKG_PATH_PROG([GTK_UPDATE_ICON_CACHE], [gtk+-2.0], [gtk-update-icon-cache]) diff --git a/src/filebuffer.cc b/src/filebuffer.cc index a7ddb1a..24a831a 100644 --- a/src/filebuffer.cc +++ b/src/filebuffer.cc @@ -27,6 +27,7 @@ #include "settings.h" #include <glib.h> +#include <glibmm.h> #include <algorithm> #include <list> #include <vector> @@ -107,6 +108,178 @@ Glib::RefPtr<RegexxerTags> RegexxerTags::instance() } // anonymous namespace +namespace +{ + +/* + * GtkSourceView has no C++ bindings, so Regexxer::FileBuffer cannot simply + * subclass it the way it used to subclass Gsv::Buffer. Instead we register a + * GObject type deriving from GtkSourceBuffer which hooks up the four + * GtkTextBufferClass vfuncs the buffer cares about, and let those trampolines + * dispatch back to the C++ virtual methods of FileBuffer. + */ +struct RegexxerSourceBuffer +{ + GtkSourceBuffer parent_instance; + Regexxer::FileBuffer* owner; +}; + +struct RegexxerSourceBufferClass +{ + GtkSourceBufferClass parent_class; +}; + +typedef void (*InsertTextFunc) (GtkTextBuffer*, GtkTextIter*, const gchar*, gint); +typedef void (*DeleteRangeFunc) (GtkTextBuffer*, GtkTextIter*, GtkTextIter*); +typedef void (*MarkDeletedFunc) (GtkTextBuffer*, GtkTextMark*); +typedef void (*ApplyTagFunc) (GtkTextBuffer*, GtkTextTag*, const GtkTextIter*, const GtkTextIter*); + +/* The vfuncs of GtkTextBufferClass we are shadowing, for the rare case of a + * vfunc being invoked before the C++ object has been fully constructed. */ +static InsertTextFunc parent_insert_text = 0; +static DeleteRangeFunc parent_delete_range = 0; +static MarkDeletedFunc parent_mark_deleted = 0; +static ApplyTagFunc parent_apply_tag = 0; + +/* The Regexxer::FileBuffer vfuncs to install, handed over by + * FileBuffer::get_source_buffer_type() which has the necessary access. */ +static InsertTextFunc self_insert_text = 0; +static DeleteRangeFunc self_delete_range = 0; +static MarkDeletedFunc self_mark_deleted = 0; +static ApplyTagFunc self_apply_tag = 0; + +static +void regexxer_source_buffer_capture_parent_vfuncs(RegexxerSourceBufferClass* klass) +{ + const GtkTextBufferClass* const parent = + GTK_TEXT_BUFFER_CLASS(g_type_class_peek_parent(klass)); + + parent_insert_text = parent->insert_text; + parent_delete_range = parent->delete_range; + parent_mark_deleted = parent->mark_deleted; + parent_apply_tag = parent->apply_tag; +} + +static +RegexxerSourceBuffer* regexxer_source_buffer_cast(GtkTextBuffer* buffer) +{ + return reinterpret_cast<RegexxerSourceBuffer*>(buffer); +} + +static +Regexxer::FileBuffer* regexxer_source_buffer_get_owner(GtkTextBuffer* buffer) +{ + return regexxer_source_buffer_cast(buffer)->owner; +} + +static +void regexxer_source_buffer_set_owner(GtkTextBuffer* buffer, Regexxer::FileBuffer* owner) +{ + regexxer_source_buffer_cast(buffer)->owner = owner; +} + +static +void regexxer_source_buffer_insert_text(GtkTextBuffer* buffer, GtkTextIter* pos, + const gchar* text, gint bytes) +{ + if (Regexxer::FileBuffer* const owner = regexxer_source_buffer_get_owner(buffer)) + { + // The C++ side gets a copy of the iterator, because that is all it may + // look at: it has to see the buffer in its pre-insertion state. The + // chain-up on the other hand must be given the caller's own iterator, + // since GTK+ advances it in place and callers rely on that to find out + // where the insertion ended up. + self_insert_text(buffer, pos, text, bytes); + + parent_insert_text(buffer, pos, text, bytes); + } + else + { + parent_insert_text(buffer, pos, text, bytes); + } +} + +static +void regexxer_source_buffer_delete_range(GtkTextBuffer* buffer, GtkTextIter* start, + GtkTextIter* end) +{ + if (Regexxer::FileBuffer* const owner = regexxer_source_buffer_get_owner(buffer)) + { + // Same as for insert_text: copy for the C++ side, original for the + // chain-up, which collapses the range in place. + self_delete_range(buffer, start, end); + + parent_delete_range(buffer, start, end); + } + else + { + parent_delete_range(buffer, start, end); + } +} + +static +void regexxer_source_buffer_mark_deleted(GtkTextBuffer* buffer, GtkTextMark* mark) +{ + if (Regexxer::FileBuffer* const owner = regexxer_source_buffer_get_owner(buffer)) + self_mark_deleted(buffer, mark); + else + parent_mark_deleted(buffer, mark); +} + +static +void regexxer_source_buffer_apply_tag(GtkTextBuffer* buffer, GtkTextTag* tag, + const GtkTextIter* start, const GtkTextIter* end) +{ + if (Regexxer::FileBuffer* const owner = regexxer_source_buffer_get_owner(buffer)) + self_apply_tag(buffer, tag, start, end); + else + parent_apply_tag(buffer, tag, start, end); +} + +static +void regexxer_source_buffer_class_init(gpointer klass, gpointer) +{ + regexxer_source_buffer_capture_parent_vfuncs( + reinterpret_cast<RegexxerSourceBufferClass*>(klass)); + + GtkTextBufferClass* const buffer_class = GTK_TEXT_BUFFER_CLASS(klass); + + buffer_class->insert_text = regexxer_source_buffer_insert_text; + buffer_class->delete_range = regexxer_source_buffer_delete_range; + buffer_class->mark_deleted = regexxer_source_buffer_mark_deleted; + buffer_class->apply_tag = regexxer_source_buffer_apply_tag; +} + +static +GType regexxer_source_buffer_register(InsertTextFunc insert_text, + DeleteRangeFunc delete_range, + MarkDeletedFunc mark_deleted, + ApplyTagFunc apply_tag) +{ + self_insert_text = insert_text; + self_delete_range = delete_range; + self_mark_deleted = mark_deleted; + self_apply_tag = apply_tag; + + GTypeInfo info; + + info.class_size = sizeof(RegexxerSourceBufferClass); + info.base_init = 0; + info.base_finalize = 0; + info.class_init = regexxer_source_buffer_class_init; + info.class_finalize = 0; + info.class_data = 0; + info.instance_size = sizeof(RegexxerSourceBuffer); + info.n_preallocs = 0; + info.instance_init = 0; + info.value_table = 0; + + return g_type_register_static(GTK_SOURCE_TYPE_BUFFER, "RegexxerSourceBuffer", + &info, static_cast<GTypeFlags>(0)); +} + +} // anonymous namespace + namespace Regexxer { @@ -160,7 +333,9 @@ public: FileBuffer::FileBuffer() : - Gsv::Buffer(Glib::RefPtr<Gtk::TextTagTable>(RegexxerTags::instance())), + Gsv::Buffer(GTK_TEXT_BUFFER(g_object_new(get_source_buffer_type(), + "tag-table", + RegexxerTags::instance()->gobj(), nullptr))), match_set_ (), current_match_ (match_set_.end()), user_action_stack_ (), @@ -172,11 +347,27 @@ FileBuffer::FileBuffer() cached_bound_state_ (BOUND_FIRST | BOUND_LAST), match_removed_ (false), locked_ (false) -{} +{ + regexxer_source_buffer_set_owner(gobj(), this); + + signal_modified_changed().connect(sigc::mem_fun(*this, &FileBuffer::on_modified_changed)); + signal_begin_user_action().connect(sigc::mem_fun(*this, &FileBuffer::on_begin_user_action)); + signal_end_user_action ().connect(sigc::mem_fun(*this, &FileBuffer::on_end_user_action)); +} FileBuffer::~FileBuffer() {} +// static +GType FileBuffer::get_source_buffer_type() +{ + static GType source_buffer_type = regexxer_source_buffer_register( + &FileBuffer::vfunc_insert_text, &FileBuffer::vfunc_delete_range, + &FileBuffer::vfunc_mark_deleted, &FileBuffer::vfunc_apply_tag); + + return source_buffer_type; +} + // static Glib::RefPtr<FileBuffer> FileBuffer::create() { @@ -575,8 +766,67 @@ void FileBuffer::undo_remove_weak(FileBufferActionRemoveMatch* ptr) /**** Regexxer::FileBuffer -- protected ************************************/ +/* + * GObject vfunc trampolines. These run as the class closure of the + * corresponding GtkTextBuffer signals, which means they see the buffer + * exactly like the gtkmm class closures used to: before the default handler + * has done its work, and only when nothing connected to the signal has + * emitted a stop emission. + */ + +// static +void FileBuffer::vfunc_insert_text(GtkTextBuffer* buffer, GtkTextIter* pos, + const gchar* text, gint bytes) +{ + FileBuffer* const self = regexxer_source_buffer_get_owner(buffer); + g_return_if_fail(self); + + self->on_insert(iterator(pos), text, bytes); +} + +// static +void FileBuffer::vfunc_delete_range(GtkTextBuffer* buffer, GtkTextIter* rbegin, + GtkTextIter* rend) +{ + FileBuffer* const self = regexxer_source_buffer_get_owner(buffer); + g_return_if_fail(self); + + self->on_erase(iterator(rbegin), iterator(rend)); +} + +// static +void FileBuffer::vfunc_mark_deleted(GtkTextBuffer* buffer, GtkTextMark* mark) +{ + FileBuffer* const self = regexxer_source_buffer_get_owner(buffer); + g_return_if_fail(self); + + // Borrowed reference, so take_copy here as well; see on_apply_tag() below. + self->on_mark_deleted(Glib::RefPtr<Mark>::cast_dynamic( + Glib::RefPtr<Glib::Object>(Glib::wrap(G_OBJECT(mark), true)))); +} + +// static +void FileBuffer::vfunc_apply_tag(GtkTextBuffer* buffer, GtkTextTag* tag, + const GtkTextIter* range_begin, + const GtkTextIter* range_end) +{ + FileBuffer* const self = regexxer_source_buffer_get_owner(buffer); + g_return_if_fail(self); + + // The tag is only borrowed for the duration of the vfunc, so wrap it with + // take_copy: Glib::wrap() otherwise assumes ownership of our reference and + // would destroy the object once the RefPtr goes out of scope. + self->on_apply_tag(Glib::RefPtr<Tag>::cast_dynamic( + Glib::RefPtr<Glib::Object>(Glib::wrap(G_OBJECT(tag), true))), + iterator(range_begin), iterator(range_end)); +} + void FileBuffer::on_insert(const FileBuffer::iterator& pos, const Glib::ustring& text, int bytes) { + // The byte count is only needed by the chain-up, which the GObject vfunc + // trampoline performs; everything below works on the ustring. + (void) bytes; + if (!text.empty()) { if (!match_removed_ && current_match_ != match_set_.end()) @@ -609,7 +859,8 @@ void FileBuffer::on_insert(const FileBuffer::iterator& pos, const Glib::ustring& new FileBufferActionInsert(*this, pos.get_offset(), text))); } - Gtk::TextBuffer::on_insert(pos, text, bytes); + // No chain-up here: regexxer_source_buffer_insert_text() does that, with the + // iterator the caller handed us, once this bookkeeping is done. } void FileBuffer::on_erase(const FileBuffer::iterator& rbegin, const FileBuffer::iterator& rend) @@ -666,7 +917,8 @@ void FileBuffer::on_erase(const FileBuffer::iterator& rbegin, const FileBuffer:: new FileBufferActionErase(*this, rbegin.get_offset(), get_slice(rbegin, rend)))); } - Gtk::TextBuffer::on_erase(rbegin, rend); + // No chain-up here: regexxer_source_buffer_delete_range() does that, with + // the iterators the caller handed us, once this bookkeeping is done. } void FileBuffer::on_mark_deleted(const Glib::RefPtr<FileBuffer::Mark>& mark) diff --git a/src/filebuffer.h b/src/filebuffer.h index 29e44f7..a5af644 100644 --- a/src/filebuffer.h +++ b/src/filebuffer.h @@ -23,9 +23,9 @@ #include "fileshared.h" #include "signalutils.h" +#include "sourceview.h" #include "undostack.h" -#include <gtksourceviewmm/buffer.h> #include <set> #include <stack> @@ -83,14 +83,32 @@ public: protected: FileBuffer(); + static GType get_source_buffer_type(); + + /* + * GtkSourceView has no C++ bindings, so the buffer's GObject vfuncs are + * hooked up by hand in filebuffer.cc. These trampolines then dispatch to + * the matching C++ virtual methods below before chaining up to the default + * handler, exactly like the gtkmm class closures used to do. + */ + static void vfunc_insert_text(GtkTextBuffer* buffer, GtkTextIter* pos, + const gchar* text, gint bytes); + static void vfunc_delete_range(GtkTextBuffer* buffer, GtkTextIter* start, + GtkTextIter* end); + static void vfunc_mark_deleted(GtkTextBuffer* buffer, GtkTextMark* mark); + static void vfunc_apply_tag(GtkTextBuffer* buffer, GtkTextTag* tag, + const GtkTextIter* start, const GtkTextIter* end); + virtual void on_insert(const iterator& pos, const Glib::ustring& text, int bytes); virtual void on_erase(const iterator& rbegin, const iterator& rend); virtual void on_mark_deleted(const Glib::RefPtr<Mark>& mark); virtual void on_apply_tag(const Glib::RefPtr<Tag>& tag, const iterator& range_begin, const iterator& range_end); - virtual void on_modified_changed(); - virtual void on_begin_user_action(); - virtual void on_end_user_action(); + + // Plain signal handlers, connected from the constructor. + void on_modified_changed(); + void on_begin_user_action(); + void on_end_user_action(); private: class ScopedLock; diff --git a/src/fileio.cc b/src/fileio.cc index aeaba4e..39f8319 100644 --- a/src/fileio.cc +++ b/src/fileio.cc @@ -26,7 +26,7 @@ #include <glib.h> #include <glibmm.h> #include <giomm.h> -#include <gtksourceviewmm.h> +#include "sourceview.h" #include <cstring> namespace @@ -157,13 +157,11 @@ void load_file(const FileInfoPtr& fileinfo, const std::string& fallback_encoding if (!buffer) throw ErrorBinaryFile(); - Glib::RefPtr<Gsv::LanguageManager> language_manager = Gsv::LanguageManager::create(); - bool uncertain = false; std::string content_type = Gio::content_type_guess(fileinfo->fullname, buffer->get_text(), uncertain); buffer->set_highlight_syntax(true); - buffer->set_language(language_manager->guess_language(fileinfo->fullname, content_type)); + buffer->set_language(Gsv::guess_language(fileinfo->fullname, content_type)); buffer->set_modified(false); fileinfo->encoding = encoding; diff --git a/src/main.cc b/src/main.cc index 851f789..bf420ac 100644 --- a/src/main.cc +++ b/src/main.cc @@ -21,6 +21,7 @@ #include "globalstrings.h" #include "mainwindow.h" #include "miscutils.h" +#include "sourceview.h" #include "translation.h" #include <glib.h> @@ -33,7 +34,6 @@ #include <gtkmm/stock.h> #include <gtkmm/stockitem.h> #include <gtkmm/window.h> -#include <gtksourceviewmm/init.h> #include <giomm/init.h> #include <exception> diff --git a/src/mainwindow.cc b/src/mainwindow.cc index 2596f5a..e27142e 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -29,7 +29,6 @@ #include <glib.h> #include <gtkmm.h> -#include <gtksourceviewmm.h> #include <algorithm> #include <functional> #include <iostream> diff --git a/src/sourceview.cc b/src/sourceview.cc new file mode 100644 index 0000000..659318b --- /dev/null +++ b/src/sourceview.cc @@ -0,0 +1,148 @@ +/* + * This file is part of regexxer. + * + * regexxer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * regexxer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with regexxer; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "sourceview.h" + +namespace Gsv +{ + +/**** Gsv::init ************************************************************/ + +void init() +{ + gtk_source_init(); +} + +/**** Gsv::guess_language **************************************************/ + +GtkSourceLanguage* guess_language(const std::string& filename, + const std::string& content_type) +{ + GtkSourceLanguageManager* const manager = + gtk_source_language_manager_get_default(); // not owned, a singleton + + return gtk_source_language_manager_guess_language(manager, filename.c_str(), + content_type.c_str()); +} + +/**** Gsv::Buffer **********************************************************/ + +Buffer::Buffer(GtkTextBuffer* castitem) +: + Gtk::TextBuffer(castitem) +{} + +Buffer::~Buffer() noexcept +{} + +GtkSourceBuffer* Buffer::source_gobj() +{ + return reinterpret_cast<GtkSourceBuffer*>(Gtk::TextBuffer::gobj()); +} + +// static +GtkSourceBuffer* Buffer::create_source_buffer( + const Glib::RefPtr< Gtk::TextBuffer::TagTable >& tag_table) +{ + return gtk_source_buffer_new(tag_table ? tag_table->gobj() : nullptr); +} + +void Buffer::set_language(GtkSourceLanguage* language) +{ + gtk_source_buffer_set_language(source_gobj(), language); +} + +void Buffer::set_highlight_syntax(bool highlight_syntax) +{ + gtk_source_buffer_set_highlight_syntax(source_gobj(), highlight_syntax); +} + +void Buffer::set_max_undo_levels(int limit) +{ + gtk_source_buffer_set_max_undo_levels(source_gobj(), limit); +} + +void Buffer::begin_not_undoable_action() +{ + gtk_source_buffer_begin_not_undoable_action(source_gobj()); +} + +void Buffer::end_not_undoable_action() +{ + gtk_source_buffer_end_not_undoable_action(source_gobj()); +} + +/**** Gsv::View ************************************************************/ + +View::View() +: + Gtk::TextView(GTK_TEXT_VIEW(gtk_source_view_new())) +{} + +View::~View() noexcept +{} + +GtkSourceView* View::source_gobj() +{ + return reinterpret_cast<GtkSourceView*>(Gtk::TextView::gobj()); +} + +void View::set_show_line_numbers(bool show_line_numbers) +{ + gtk_source_view_set_show_line_numbers(source_gobj(), show_line_numbers); +} + +void View::set_highlight_current_line(bool highlight_current_line) +{ + gtk_source_view_set_highlight_current_line(source_gobj(), highlight_current_line); +} + +void View::set_auto_indent(bool auto_indent) +{ + gtk_source_view_set_auto_indent(source_gobj(), auto_indent); +} + +void View::set_draw_spaces(DrawSpacesFlags flags) +{ + // The DrawSpacesFlags bits are reinterpreted as GtkSourceSpaceType bits + // below, so keep the two in step. They are compared as plain integers + // because comparing two distinct enum types directly trips -Wenum-compare. + // GtkSourceView dropped its old gtk_source_view_set_draw_spaces() entry + // point in favour of a per-view space drawer, which is retrieved as a + // borrowed pointer that is valid for the lifetime of the view and + // configured in place. + static_assert (static_cast<int> (DRAW_SPACES_SPACE) == + static_cast<int> (GTK_SOURCE_SPACE_TYPE_SPACE), ""); + static_assert (static_cast<int> (DRAW_SPACES_TAB) == + static_cast<int> (GTK_SOURCE_SPACE_TYPE_TAB), ""); + static_assert (static_cast<int> (DRAW_SPACES_NEWLINE) == + static_cast<int> (GTK_SOURCE_SPACE_TYPE_NEWLINE), ""); + static_assert (static_cast<int> (DRAW_SPACES_NBSP) == + static_cast<int> (GTK_SOURCE_SPACE_TYPE_NBSP), ""); + static_assert (static_cast<int> (DRAW_SPACES_ALL) == + static_cast<int> (GTK_SOURCE_SPACE_TYPE_ALL), ""); + + GtkSourceSpaceDrawer* const drawer = // not owned, owned by the view + gtk_source_view_get_space_drawer(source_gobj()); + + gtk_source_space_drawer_set_types_for_locations( + drawer, GTK_SOURCE_SPACE_LOCATION_ALL, + static_cast<GtkSourceSpaceTypeFlags> (flags)); +} + +} // namespace Gsv diff --git a/src/sourceview.h b/src/sourceview.h new file mode 100644 index 0000000..f689a37 --- /dev/null +++ b/src/sourceview.h @@ -0,0 +1,111 @@ +/* + * This file is part of regexxer. + * + * regexxer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * regexxer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with regexxer; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef REGEXXER_SOURCEVIEW_H_INCLUDED +#define REGEXXER_SOURCEVIEW_H_INCLUDED + +#include <glibmm/refptr.h> +#include <glibmm/ustring.h> +#include <gtkmm/textbuffer.h> +#include <gtkmm/textview.h> + +#include <gtksourceview/gtksource.h> + +#include <string> + +/* + * Minimal C++ bindings for the handful of GtkSourceView 4 classes regexxer + * actually uses. GtkSourceView ships plain C only, and gtksourceviewmm has + * been unmaintained for well over a decade, so rather than binding the whole + * library we just wrap what we need here. The GtkSourceView types themselves + * remain visible in this interface, which is fine as long as callers stay + * within the trivial getters and setters implemented in sourceview.cc. + */ +namespace Gsv +{ + +/* + * Which kinds of whitespace get drawn in the text view. GtkSourceView 4 + * replaced its old draw-spaces toggle with a space drawer object, and + * regexxer only ever cared about a plain bitmask, so the bits are kept here + * and translated in sourceview.cc. The values deliberately match the + * GtkSourceSpaceType bits so the translation stays a plain cast; static_asserts + * in sourceview.cc guard that. + */ +enum DrawSpacesFlags +{ + DRAW_SPACES_SPACE = 1, + DRAW_SPACES_TAB = 2, + DRAW_SPACES_NEWLINE = 4, + DRAW_SPACES_NBSP = 8, + DRAW_SPACES_ALL = 15 +}; + +/* + * Registers the GtkSourceView types with the GObject type system. Must be + * called before the first widget or buffer is created. + */ +void init(); + +/* + * Guess the syntax highlighting language for a file. The result is owned by + * the language manager and is only valid as long as it is referenced by + * something, e.g. a buffer it has been passed to. A null pointer is + * returned if the file cannot be identified as a known language. + */ +GtkSourceLanguage* guess_language(const std::string& filename, + const std::string& content_type); + +class Buffer : public Gtk::TextBuffer +{ +public: + ~Buffer() noexcept override; + + void set_language(GtkSourceLanguage* language); + void set_highlight_syntax(bool highlight_syntax = true); + void set_max_undo_levels(int limit); + void begin_not_undoable_action(); + void end_not_undoable_action(); + +protected: + explicit Buffer(GtkTextBuffer* castitem); + + GtkSourceBuffer* source_gobj(); + + static GtkSourceBuffer* create_source_buffer( + const Glib::RefPtr< Gtk::TextBuffer::TagTable >& tag_table); +}; + +class View : public Gtk::TextView +{ +public: + View(); + ~View() noexcept override; + + void set_show_line_numbers(bool show_line_numbers = true); + void set_highlight_current_line(bool highlight_current_line = true); + void set_auto_indent(bool auto_indent = true); + void set_draw_spaces(DrawSpacesFlags flags); + +protected: + GtkSourceView* source_gobj(); +}; + +} // namespace Gsv + +#endif /* REGEXXER_SOURCEVIEW_H_INCLUDED */

