Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
syntax.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
4/* Authors:
5 * Rafael Siejakowski <rs@rs-math.net>
6 * Mike Kowalski
7 *
8 * Copyright (C) 2022 Authors
9 *
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#include "ui/syntax.h"
14
15#include <glibmm/ustring.h>
16#include <pango/pango-attributes.h>
17#include <sstream>
18#include <stdexcept>
19#include <string>
20
21#include "config.h"
22#include "io/resource.h"
23#include "object/sp-factory.h"
24#include "util/trim.h"
25
26#if WITH_GSOURCEVIEW
27# include <gtksourceview/gtksource.h>
28#endif
29
31
32Glib::ustring XMLFormatter::_format(Style const &style, Glib::ustring const &content) const
33{
34 return _format(style, content.c_str());
35}
36
38Glib::ustring Style::openingTag() const
39{
40 if (isDefault()) {
41 return "";
42 }
43
44 std::ostringstream ost;
45 ost << "<span";
46 if (color) {
47 ost << " color=\"" << color->raw() << '"';
48 }
49 if (background) {
50 ost << " bgcolor=\"" << background->raw() << '"';
51 }
52 if (bold) {
53 ost << " weight=\"bold\"";
54 }
55 if (italic) {
56 ost << " font_style=\"italic\"";
57 }
58 if (underline) {
59 ost << " underline=\"single\"";
60 }
61
62 ost << ">";
63 return Glib::ustring(ost.str());
64}
65
67Glib::ustring Style::closingTag() const
68{
69 return isDefault() ? "" : "</span>";
70}
71
72Glib::ustring quote(const char* text)
73{
74 return Glib::ustring::compose("\"%1\"", text);
75}
76
78void XMLFormatter::openTag(char const *tag_name)
79{
81
82 // Highlight as errors unsupported tags in SVG namespace (explicit or implicit).
83 bool error = false;
84 std::string fully_qualified_name(tag_name);
85 if (fully_qualified_name.empty()) {
86 return;
87 }
88 bool is_svg = false;
89 if (fully_qualified_name.find(':') == std::string::npos) {
90 fully_qualified_name = std::string("svg:") + fully_qualified_name;
91 is_svg = true;
92 } else if (fully_qualified_name.find("svg:") == 0) {
93 is_svg = true;
94 }
95 if (is_svg && !SPFactory::supportsType(fully_qualified_name)) {
96 error = true;
97 }
98 _wip += _format(error ? _style.error : _style.tag_name, tag_name);
99}
100
101void XMLFormatter::addAttribute(char const *name, char const *value)
102{
103 _wip += Glib::ustring::compose(" %1%2%3",
107}
108
109Glib::ustring XMLFormatter::finishTag(bool self_close)
110{
111 return _wip + _format(_style.angular_brackets, self_close ? "/>" : ">");
112}
113
114Glib::ustring XMLFormatter::formatContent(char const* content, bool wrap_in_quotes) const
115{
116 Glib::ustring text = wrap_in_quotes ? quote(content) : content;
117 return _format(_style.content, text);
118}
119
120Glib::ustring XMLFormatter::formatComment(char const* comment, bool wrap_in_marks) const
121{
122 if (wrap_in_marks) {
123 auto wrapped = Glib::ustring::compose("<!--%1-->", comment);
124 return _format(_style.comment, wrapped.c_str());
125 }
126 return _format(_style.comment, comment);
127}
128
129XMLStyles build_xml_styles(const Glib::ustring& syntax_theme)
130{
131 XMLStyles styles;
132
133#if WITH_GSOURCEVIEW
134 auto manager = gtk_source_style_scheme_manager_get_default();
135 if (auto scheme = gtk_source_style_scheme_manager_get_scheme(manager, syntax_theme.c_str())) {
136
137 auto get_color = [](GtkSourceStyle* style, const char* prop) -> std::optional<Glib::ustring> {
138 std::optional<Glib::ustring> maybe_color;
139 Glib::ustring name(prop);
140 gboolean set;
141 gchar* color = 0;
142 g_object_get(style, (name + "-set").c_str(), &set, name.c_str(), &color, nullptr);
143 if (set && color && *color == '#') {
144 maybe_color = Glib::ustring(color);
145 }
146 g_free(color);
147 return maybe_color;
148 };
149
150 auto get_bool = [](GtkSourceStyle* style, const char* prop, bool def = false) -> bool {
151 Glib::ustring name(prop);
152 gboolean set;
153 gboolean flag;
154 g_object_get(style, (name + "-set").c_str(), &set, name.c_str(), &flag, nullptr);
155 return set ? !!flag : def;
156 };
157
158 auto get_underline = [](GtkSourceStyle* style, bool def = false) -> bool {
159 Glib::ustring name("underline");
160 gboolean set;
161 PangoUnderline underline;
162 g_object_get(style, (name + "-set").c_str(), &set, ("pango-" + name).c_str(), &underline, nullptr);
163 return set ? underline != PANGO_UNDERLINE_NONE : def;
164 };
165
166 auto to_style = [&](char const *id) -> Style {
167 auto s = gtk_source_style_scheme_get_style(scheme, id);
168 if (!s) {
169 return Style();
170 }
171
172 Style style;
173
174 style.color = get_color(s, "foreground");
175 style.background = get_color(s, "background");
176 style.bold = get_bool(s, "bold");
177 style.italic = get_bool(s, "italic");
178 style.underline = get_underline(s);
179
180 return style;
181 };
182
183 styles.tag_name = to_style("def:statement");
184 styles.attribute_name = to_style("def:number");
185 styles.attribute_value = to_style("def:string");
186 styles.content = to_style("def:string");
187 styles.comment = to_style("def:comment");
188 styles.prolog = to_style("def:warning");
189 styles.angular_brackets = to_style("draw-spaces");
190 styles.error = to_style("def:error");
191 }
192#endif
193
194 return styles;
195}
196
199Glib::ustring prettify_css(Glib::ustring const &css)
200{
201 // Ensure that there's a space after every colon, unless there's a slash (as in a URL).
202 static auto const colon_without_space = Glib::Regex::create(":([^\\s\\/])");
203 auto reformatted = colon_without_space->replace(css, 0, ": \\1", Glib::Regex::MatchFlags::NOTEMPTY);
204 // Ensure that there's a newline after every semicolon.
205 static auto const semicolon_without_newline = Glib::Regex::create(";([^\r\n])");
206 reformatted = semicolon_without_newline->replace(reformatted, 0, ";\n\\1", Glib::Regex::MatchFlags::NEWLINE_ANYCRLF);
207 // If the last character is not a semicolon, append one.
208 if (auto len = css.size(); len && css[len - 1] != ';') {
209 reformatted += ";";
210 }
211 return reformatted;
212}
213
215Glib::ustring minify_css(Glib::ustring const &css)
216{
217 static auto const space_after = Glib::Regex::create("(:|;)[\\s]+");
218 auto minified = space_after->replace(css, 0, "\\1", Glib::Regex::MatchFlags::NEWLINE_ANY);
219 // Strip final semicolon
220 if (auto const len = minified.size(); len && minified[len - 1] == ';') {
221 minified = minified.erase(len - 1);
222 }
223 return minified;
224}
225
227Glib::ustring prettify_svgd(Glib::ustring const &d)
228{
229 auto result = d;
231 // Ensure that a non-M command is preceded only by a newline.
232 static auto const space_b4_command = Glib::Regex::create("(?<=\\S)\\s*(?=[LHVCSQTAZlhvcsqtaz])");
233 result = space_b4_command->replace(result, 1, "\n", Glib::Regex::MatchFlags::NEWLINE_ANY);
234
235 // Before a non-initial M command, we want to have two newlines to visually separate the subpaths.
236 static auto const space_b4_m = Glib::Regex::create("(?<=\\S)\\s*(?=[Mm])");
237 result = space_b4_m->replace(result, 1, "\n\n", Glib::Regex::MatchFlags::NEWLINE_ANY);
238
239 // Ensure that there's a space after each command letter other than Z.
240 static auto const nospace = Glib::Regex::create("([MLHVCSQTAmlhvcsqta])(?=\\S)");
241 return nospace->replace(result, 0, "\\1 ", Glib::Regex::MatchFlags::NEWLINE_ANY);
242}
243
245Glib::ustring minify_svgd(Glib::ustring const &d)
246{
247 static auto const excessive_space = Glib::Regex::create("[\\s]+");
248 auto result = excessive_space->replace(d, 0, " ", Glib::Regex::MatchFlags::NEWLINE_ANY);
250 return result;
251}
252
254static void init_text_view(Gtk::TextView* textview)
255{
256 textview->set_wrap_mode(Gtk::WrapMode::WORD);
257 textview->set_editable(true);
258 textview->set_visible(true);
259}
260
262class PlainTextView : public TextEditView
263{
264public:
265 PlainTextView()
266 : _textview(std::make_unique<Gtk::TextView>(Gtk::TextBuffer::create()))
267 {
268 init_text_view(_textview.get());
269 }
270
271 void setStyle(const Glib::ustring& theme) override { /* no op */ }
272 void setText(const Glib::ustring& text) override { _textview->get_buffer()->set_text(text); }
273
274 Glib::ustring getText() const override { return _textview->get_buffer()->get_text(); }
275 Gtk::TextView& getTextView() const override { return *_textview; }
276
277private:
278 std::unique_ptr<Gtk::TextView> _textview;
279};
280
281#if WITH_GSOURCEVIEW
282
286static GtkSourceLanguageManager* get_language_manager()
287{
289 auto default_manager = gtk_source_language_manager_get_default();
290 auto default_paths = gtk_source_language_manager_get_search_path(default_manager);
291
292 std::vector<char const *> all_paths;
293 for (auto path = default_paths; *path; path++) {
294 all_paths.push_back(*path);
295 }
296 all_paths.push_back(ui_path.c_str());
297 all_paths.push_back(nullptr);
298
299 auto result = gtk_source_language_manager_new();
300 gtk_source_language_manager_set_search_path(result, (gchar **)all_paths.data());
301 return result;
302}
303
304class SyntaxHighlighting : public TextEditView
305{
306public:
307 SyntaxHighlighting() = delete;
309 SyntaxHighlighting(char const* const language,
310 Glib::ustring (*prettify_func)(Glib::ustring const &),
311 Glib::ustring (*minify_func)(Glib::ustring const &))
312 : _prettify{prettify_func}
313 , _minify{minify_func}
314 {
315 auto lang = get_language(language);
316 _buffer = gtk_source_buffer_new_with_language(lang);
317 auto view = gtk_source_view_new_with_buffer(_buffer);
318 // Increment Glib's internal refcount to prevent the destruction of the
319 // textview by a parent widget (if any); the textview is owned by us!
320 g_object_ref(view);
321
322 _textview = std::unique_ptr<Gtk::TextView>(Glib::wrap((GtkTextView*)view));
323 if (!_textview) {
324 // don't crash when sourceview cannot be created; substitute with a regular one;
325 // in this case GTK has already outputted warnings
326 _textview = std::make_unique<Gtk::TextView>(Gtk::TextBuffer::create());
327 }
328 init_text_view(_textview.get());
329 }
330
331 ~SyntaxHighlighting() override { g_object_unref(_buffer); }
332private:
333 GtkSourceBuffer *_buffer = nullptr; // Owned by us
334 std::unique_ptr<Gtk::TextView> _textview;
335 Glib::ustring (*_prettify)(Glib::ustring const &);
336 Glib::ustring (*_minify)(Glib::ustring const &);
337 static std::map<std::string, GtkSourceLanguage*> _languages;
338
339 // get requested language; cache it, since it's an expensive operation
340 static GtkSourceLanguage* get_language(const char* language) {
341 GtkSourceLanguage* lang = nullptr;
342 auto l = std::string(language);
343 auto it = _languages.find(l);
344 if (it == _languages.end()) {
345 auto manager = get_language_manager();
346 lang = gtk_source_language_manager_get_language(manager, language);
347 _languages[l] = lang;
348 }
349 else {
350 lang = it->second;
351 }
352 return lang;
353 }
354public:
355 void setStyle(Glib::ustring const &theme) override
356 {
357 auto manager = gtk_source_style_scheme_manager_get_default();
358 auto scheme = gtk_source_style_scheme_manager_get_scheme(manager, theme.c_str());
359 gtk_source_buffer_set_style_scheme(_buffer, scheme);
360 }
361
363 void setText(Glib::ustring const &text) override
364 {
365 _textview->get_buffer()->set_text(_prettify(text));
366 }
367
369 Glib::ustring getText() const override
370 {
371 return _minify(_textview->get_buffer()->get_text());
372 }
373
374 Gtk::TextView &getTextView() const override { return *_textview; };
375};
376
377std::map<std::string, GtkSourceLanguage*> SyntaxHighlighting::_languages;
378
379#endif // WITH_GSOURCEVIEW
380
382std::unique_ptr<TextEditView> TextEditView::create(SyntaxMode mode)
383{
384#if WITH_GSOURCEVIEW
385 auto const no_reformat = [](auto &s) { return s; };
386 switch (mode) {
388 return std::make_unique<PlainTextView>();
390 return std::make_unique<SyntaxHighlighting>("inline-css", &prettify_css, &minify_css);
392 return std::make_unique<SyntaxHighlighting>("css", no_reformat, no_reformat);
394 return std::make_unique<SyntaxHighlighting>("svgd", &prettify_svgd, &minify_svgd);
396 return std::make_unique<SyntaxHighlighting>("svgpoints", no_reformat, no_reformat);
398 return std::make_unique<SyntaxHighlighting>("js", no_reformat, no_reformat);
399 default:
400 throw std::runtime_error("Missing case statement in TetxEditView::create()");
401 }
402#else
403 return std::make_unique<PlainTextView>();
404#endif
405}
406
407} // namespace Inkscape::UI::Syntax
408
409/*
410 Local Variables:
411 mode:c++
412 c-file-style:"stroustrup"
413 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
414 indent-tabs-mode:nil
415 fill-column:99
416 End:
417*/
418// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
static std::unique_ptr< TextEditView > create(SyntaxMode mode)
Create a styled text view using the desired syntax highlighting mode.
Definition syntax.cpp:382
Glib::ustring _format(Style const &style, Glib::ustring const &content) const
Definition syntax.cpp:32
void openTag(char const *tag_name)
Open a new XML tag with the given tag name.
Definition syntax.cpp:78
Glib::ustring formatComment(char const *comment, bool wrap_in_comment_marks=true) const
Definition syntax.cpp:120
Glib::ustring finishTag(bool self_close=false)
Definition syntax.cpp:109
void addAttribute(char const *attribute_name, char const *attribute_value)
Definition syntax.cpp:101
Glib::ustring formatContent(char const *content, bool wrap_in_quotes=true) const
Definition syntax.cpp:114
std::shared_ptr< Css const > css
Css & result
Definition desktop.h:50
std::string get_path_string(Domain domain, Type type, char const *filename, char const *extra)
Definition resource.cpp:148
XMLStyles build_xml_styles(const Glib::ustring &syntax_theme)
Build XML styles from a GTKSourceView syntax color theme.
Definition syntax.cpp:129
Glib::ustring minify_svgd(Glib::ustring const &d)
Remove excessive space, including newlines, from a path 'd' attibute.
Definition syntax.cpp:245
Glib::ustring prettify_css(Glib::ustring const &css)
Reformat CSS for better readability.
Definition syntax.cpp:199
static GtkSourceLanguageManager * get_language_manager()
Return a pointer to a language manager which is aware of both default and custom syntaxes.
Definition syntax.cpp:286
Glib::ustring quote(const char *text)
Definition syntax.cpp:72
Glib::ustring minify_css(Glib::ustring const &css)
Undo the CSS prettification by stripping some whitespace from CSS markup.
Definition syntax.cpp:215
Glib::ustring prettify_svgd(Glib::ustring const &d)
Reformat a path 'd' attibute for better readability.
Definition syntax.cpp:227
static void init_text_view(Gtk::TextView *textview)
Set default options on a TextView widget used for syntax-colored editing.
Definition syntax.cpp:254
SyntaxMode
Syntax highlighting mode (language).
Definition syntax.h:98
@ CssStyle
File-scope CSS (contents of a CSS file or a <style> tag).
@ PlainText
Plain text (no highlighting).
@ SvgPolyPoints
Contents of the 'points' attribute of <polyline> or <polygon>.
@ SvgPathData
Contents of the 'd' attribute of the SVG <path> element.
@ InlineCss
Inline CSS (contents of a style="..." attribute).
void trim(Glib::ustring &input, Glib::ustring const &also_remove="")
Modifies a string in place, removing leading and trailing whitespace characters.
Definition trim.h:34
STL namespace.
int mode
Inkscape::IO::Resource - simple resource API.
auto len
Definition safe-printf.h:21
The style of a single element in a (Pango markup)-enabled widget.
Definition syntax.h:26
bool isDefault() const
Definition syntax.h:39
std::optional< Glib::ustring > background
Definition syntax.h:28
Glib::ustring openingTag() const
Get the opening tag of the Pango markup for this style.
Definition syntax.cpp:38
std::optional< Glib::ustring > color
Definition syntax.h:27
Glib::ustring closingTag() const
Get the closing tag of Pango markup for this style.
Definition syntax.cpp:67
The styles used for simple XML syntax highlighting.
Definition syntax.h:46
static bool supportsType(std::string const &id)
std::unique_ptr< Toolbar >(* create)()
Definition toolbars.cpp:56
Glib::ustring name
Definition toolbars.cpp:55
Inkscape::Util::trim - trim whitespace and other characters.