[valadoc] libvaladoc: Add highlighter for C, XML, Vala
- From: Florian Brosch <flobrosch src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [valadoc] libvaladoc: Add highlighter for C, XML, Vala
- Date: Sun, 10 Jan 2016 22:11:36 +0000 (UTC)
commit 7d4ff2b0d1443c6996ec99c6bf3a71ceb12206be
Author: Florian Brosch <flo brosch gmail com>
Date: Sun Jan 10 21:16:44 2016 +0100
libvaladoc: Add highlighter for C, XML, Vala
icons/devhelpstyle.css | 15 +
icons/style.css | 55 ++
icons/wikistyle.css | 14 +
src/libvaladoc/Makefile.am | 5 +
src/libvaladoc/api/tree.vala | 11 +
src/libvaladoc/content/run.vala | 66 +++-
src/libvaladoc/content/sourcecode.vala | 33 ++-
src/libvaladoc/documentation/commentscanner.vala | 2 +-
.../documentation/gtkdoccommentparser.vala | 37 +-
.../documentation/gtkdocmarkdownparser.vala | 4 +
src/libvaladoc/documentation/wikiscanner.vala | 18 +-
src/libvaladoc/highlighter/codescanner.vala | 572 ++++++++++++++++++++
src/libvaladoc/highlighter/codetoken.vala | 58 ++
src/libvaladoc/highlighter/highlighter.vala | 366 +++++++++++++
src/libvaladoc/highlighter/scanner.vala | 32 ++
src/libvaladoc/highlighter/xmlscanner.vala | 374 +++++++++++++
src/libvaladoc/html/htmlrenderer.vala | 46 ++-
17 files changed, 1683 insertions(+), 25 deletions(-)
---
diff --git a/icons/devhelpstyle.css b/icons/devhelpstyle.css
index 1488f1f..8de747b 100644
--- a/icons/devhelpstyle.css
+++ b/icons/devhelpstyle.css
@@ -125,6 +125,21 @@ div.site_body {
color: #a52a2a;
}
+.main_source .main_literal {
+ text-decoration: none;
+ color: #ff0000;
+}
+
+.main_comment {
+ text-decoration: none;
+ color: #888a85;
+}
+
+.main_preprocessor {
+ text-decoration: none;
+ color: #ad7fa8;
+}
+
div.main_code_definition {
padding-right: 10px;
padding-left: 10px;
diff --git a/icons/style.css b/icons/style.css
index 4f65fdb..c4ffc13 100644
--- a/icons/style.css
+++ b/icons/style.css
@@ -139,6 +139,61 @@ div.site_navigation {
color: #a52a2a;
}
+.main_escape {
+ text-decoration: none;
+ color: #6a5acd;
+}
+
+.xml_cdata {
+ text-decoration: none;
+ font-weight: normal;
+ color: #a020f0;
+}
+
+.xml_escape {
+ text-decoration: none;
+ font-weight: normal;
+ color: #a020f0;
+}
+
+.xml_element {
+ text-decoration: none;
+ font-weight: normal;
+ color: #008a8c;
+}
+
+.xml_attribute {
+ text-decoration: none;
+ font-weight: bold;
+ color: #6a5acd;
+}
+
+.xml_attribute_value {
+ text-decoration: none;
+ font-weight: normal;
+ color: #ff00ff;
+}
+
+.xml_comment {
+ text-decoration: none;
+ font-weight: normal;
+ color: #0202ff;
+}
+
+.main_source .main_literal {
+ text-decoration: none;
+ color: #ff00ff;
+}
+
+.main_comment {
+ text-decoration: none;
+ color: #888a85;
+}
+
+.main_preprocessor {
+ text-decoration: none;
+ color: #ad7fa8;
+}
div.main_code_definition {
padding-right: 10px;
diff --git a/icons/wikistyle.css b/icons/wikistyle.css
index ccb7022..459f91a 100644
--- a/icons/wikistyle.css
+++ b/icons/wikistyle.css
@@ -156,6 +156,20 @@ div.site_navigation {
color: #a52a2a;
}
+.main_source .main_literal {
+ text-decoration: none;
+ color: #ff0000;
+}
+
+.main_comment {
+ text-decoration: none;
+ color: #888a85;
+}
+
+.main_preprocessor {
+ text-decoration: none;
+ color: #ad7fa8;
+}
div.main_code_definition {
padding-right: 10px;
diff --git a/src/libvaladoc/Makefile.am b/src/libvaladoc/Makefile.am
index a4870ee..a53b088 100644
--- a/src/libvaladoc/Makefile.am
+++ b/src/libvaladoc/Makefile.am
@@ -161,6 +161,11 @@ libvaladoc_la_VALASOURCES = \
taglets/tagletsee.vala \
taglets/tagletsince.vala \
taglets/tagletthrows.vala \
+ highlighter/scanner.vala \
+ highlighter/codescanner.vala \
+ highlighter/xmlscanner.vala \
+ highlighter/codetoken.vala \
+ highlighter/highlighter.vala \
html/basicdoclet.vala \
html/htmlchartfactory.vala \
html/linkhelper.vala \
diff --git a/src/libvaladoc/api/tree.vala b/src/libvaladoc/api/tree.vala
index 8464926..a5ae3c5 100644
--- a/src/libvaladoc/api/tree.vala
+++ b/src/libvaladoc/api/tree.vala
@@ -35,6 +35,7 @@ public class Valadoc.Api.Tree {
private Package source_package = null;
private Settings settings;
private ErrorReporter reporter;
+ private Highlighter.Highlighter _highlighter;
private CTypeResolver _cresolver = null;
private Package _source_package;
@@ -59,6 +60,16 @@ public class Valadoc.Api.Tree {
get;
}
+ public Highlighter.Highlighter highlighter {
+ get {
+ if (_highlighter == null) {
+ _highlighter = new Highlighter.Highlighter ();
+ }
+
+ return _highlighter;
+ }
+ }
+
/**
* The root of the wiki tree.
*/
diff --git a/src/libvaladoc/content/run.vala b/src/libvaladoc/content/run.vala
index e7b19d4..3374917 100644
--- a/src/libvaladoc/content/run.vala
+++ b/src/libvaladoc/content/run.vala
@@ -35,7 +35,17 @@ public class Valadoc.Content.Run : InlineContent, Inline {
LANG_KEYWORD,
LANG_LITERAL,
LANG_BASIC_TYPE,
- LANG_TYPE;
+ LANG_TYPE,
+ LANG_PREPROCESSOR,
+ LANG_COMMENT,
+ LANG_ESCAPE,
+
+ XML_ESCAPE,
+ XML_ELEMENT,
+ XML_ATTRIBUTE,
+ XML_ATTRIBUTE_VALUE,
+ XML_COMMENT,
+ XML_CDATA;
public static Style? from_string (string str) {
switch (str) {
@@ -57,6 +67,9 @@ public class Valadoc.Content.Run : InlineContent, Inline {
case "stroke":
return Style.STROKE;
+ case "lang-escape":
+ return Style.LANG_ESCAPE;
+
case "lang-keyword":
return Style.LANG_KEYWORD;
@@ -68,6 +81,30 @@ public class Valadoc.Content.Run : InlineContent, Inline {
case "lang-type":
return Style.LANG_TYPE;
+
+ case "lang-preprocessor":
+ return Style.LANG_PREPROCESSOR;
+
+ case "lang-comment":
+ return Style.LANG_COMMENT;
+
+ case "xml-escape":
+ return Style.XML_ESCAPE;
+
+ case "xml-element":
+ return Style.XML_ELEMENT;
+
+ case "xml-attribute":
+ return Style.XML_ATTRIBUTE;
+
+ case "xml-attribute-value":
+ return Style.XML_ATTRIBUTE_VALUE;
+
+ case "xml-comment":
+ return Style.XML_COMMENT;
+
+ case "xml-cdata":
+ return Style.XML_CDATA;
}
return null;
@@ -93,6 +130,9 @@ public class Valadoc.Content.Run : InlineContent, Inline {
case Style.STROKE:
return "stroke";
+ case Style.LANG_ESCAPE:
+ return "lang-escape";
+
case Style.LANG_KEYWORD:
return "lang-keyword";
@@ -104,6 +144,30 @@ public class Valadoc.Content.Run : InlineContent, Inline {
case Style.LANG_TYPE:
return "lang-type";
+
+ case Style.LANG_PREPROCESSOR:
+ return "lang-preprocessor";
+
+ case Style.LANG_COMMENT:
+ return "lang-comment";
+
+ case Style.XML_ESCAPE:
+ return "xml-escape";
+
+ case Style.XML_ELEMENT:
+ return "xml-element";
+
+ case Style.XML_ATTRIBUTE:
+ return "xml-attribute";
+
+ case Style.XML_ATTRIBUTE_VALUE:
+ return "xml-attribute-value";
+
+ case Style.XML_COMMENT:
+ return "xml-comment";
+
+ case Style.XML_CDATA:
+ return "xml-cdata";
}
assert (true);
diff --git a/src/libvaladoc/content/sourcecode.vala b/src/libvaladoc/content/sourcecode.vala
index 8e9fca3..31803f0 100644
--- a/src/libvaladoc/content/sourcecode.vala
+++ b/src/libvaladoc/content/sourcecode.vala
@@ -27,6 +27,7 @@ public class Valadoc.Content.SourceCode : ContentElement, Inline {
public enum Language {
GENIE,
VALA,
+ XML,
C;
public static Language? from_path (string path) {
@@ -50,6 +51,9 @@ public class Valadoc.Content.SourceCode : ContentElement, Inline {
case "gs":
return Language.GENIE;
+ case "xml":
+ return Language.XML;
+
case "vala":
return Language.VALA;
@@ -69,6 +73,9 @@ public class Valadoc.Content.SourceCode : ContentElement, Inline {
case Language.VALA:
return "vala";
+ case Language.XML:
+ return "xml";
+
case Language.C:
return "c";
}
@@ -78,11 +85,17 @@ public class Valadoc.Content.SourceCode : ContentElement, Inline {
}
}
+
public string code {
get;
set;
}
+ public Run? highlighted_code {
+ get;
+ private set;
+ }
+
public Language? language {
get;
set;
@@ -161,6 +174,7 @@ public class Valadoc.Content.SourceCode : ContentElement, Inline {
return string.joinv ("\n", (string[]) _lines);
}
+
public override void check (Api.Tree api_root, Api.Node container, string file_path,
ErrorReporter reporter, Settings settings)
{
@@ -180,18 +194,35 @@ public class Valadoc.Content.SourceCode : ContentElement, Inline {
if (_language == null && name != "none") {
string node_segment = (container is Api.Package)? "" :
container.get_full_name () + ": ";
reporter.simple_warning ("%s: %s{{{".printf (file_path, node_segment),
- "Unsupported
programming language '%s'", name);
+ "Unsupported programming language '%s'", name);
}
}
}
code = strip_code (code);
+
+ if (_language == Language.VALA) {
+ highlighted_code = api_root.highlighter.highlight_vala (code);
+ } else if (_language == Language.XML) {
+ highlighted_code = api_root.highlighter.highlight_xml (code);
+ } else if (_language == Language.C) {
+ highlighted_code = api_root.highlighter.highlight_c (code);
+ } else {
+ highlighted_code = new Run (Run.Style.MONOSPACED);
+ highlighted_code.content.add (new Text (code));
+ }
}
public override void accept (ContentVisitor visitor) {
visitor.visit_source_code (this);
}
+ public override void accept_children (ContentVisitor visitor) {
+ if (highlighted_code != null) {
+ highlighted_code.accept (visitor);
+ }
+ }
+
public override bool is_empty () {
// empty source blocks are visible as well
return false;
diff --git a/src/libvaladoc/documentation/commentscanner.vala
b/src/libvaladoc/documentation/commentscanner.vala
index 4f96875..ad01601 100644
--- a/src/libvaladoc/documentation/commentscanner.vala
+++ b/src/libvaladoc/documentation/commentscanner.vala
@@ -53,7 +53,7 @@ public class Valadoc.CommentScanner : WikiScanner {
base.accept (c);
in_line_start = true;
start_column = 0;
- } else if (c == ' ') {
+ } else {
in_line_start = false;
}
}
diff --git a/src/libvaladoc/documentation/gtkdoccommentparser.vala
b/src/libvaladoc/documentation/gtkdoccommentparser.vala
index 0f4babe..81dc52a 100644
--- a/src/libvaladoc/documentation/gtkdoccommentparser.vala
+++ b/src/libvaladoc/documentation/gtkdoccommentparser.vala
@@ -48,6 +48,7 @@ public class Valadoc.Gtkdoc.Parser : Object, ResourceLocator {
private Regex? is_numeric_regex = null;
private Regex? normalize_regex = null;
+ private Regex regex_source_lang = null;
private Importer.InternalIdRegistrar id_registrar = null;
private GirMetaData? current_metadata = null;
@@ -79,6 +80,10 @@ public class Valadoc.Gtkdoc.Parser : Object, ResourceLocator {
}
private void report_unexpected_token (Token got, string expected) {
+ report_warning (got, "Unexpected Token: %s (Expected: %s)".printf (got.to_string (),
expected));
+ }
+
+ private void report_warning (Token got, string message) {
if (!this.show_warnings) {
return ;
}
@@ -95,9 +100,7 @@ public class Valadoc.Gtkdoc.Parser : Object, ResourceLocator {
startpos + 1,
endpos + 1,
this.comment_lines[got.line],
- "Unexpected Token: %s (Expected: %s)",
- got.to_string (),
- expected);
+ message);
}
public Parser (Settings settings, ErrorReporter reporter, Api.Tree tree, ModuleLoader modules) {
@@ -110,6 +113,7 @@ public class Valadoc.Gtkdoc.Parser : Object, ResourceLocator {
is_numeric_regex = new Regex
("^[+-]?([0-9]*\\.?[0-9]+|[0-9]+\\.?[0-9]*)([eE][+-]?[0-9]+)?$",
RegexCompileFlags.OPTIMIZE);
normalize_regex = new Regex ("( |\n|\t)+", RegexCompileFlags.OPTIMIZE);
+ regex_source_lang = new Regex ("^<!--[ \t]+language=\"([A-Za-z]*)\"[ \t]+-->");
} catch (RegexError e) {
assert_not_reached ();
}
@@ -692,8 +696,8 @@ public class Valadoc.Gtkdoc.Parser : Object, ResourceLocator {
return null;
}
-
StringBuilder builder = new StringBuilder ();
+ Token source_token = current;
for (next (); current.type != TokenType.EOF && current.type != TokenType.GTKDOC_SOURCE_CLOSE;
next ()) {
if (current.type == TokenType.WORD) {
@@ -703,12 +707,29 @@ public class Valadoc.Gtkdoc.Parser : Object, ResourceLocator {
}
}
- SourceCode src = factory.create_source_code ();
- src.language = SourceCode.Language.C;
- src.code = builder.str;
+ SourceCode code = factory.create_source_code ();
+ MatchInfo info;
+
+ unowned string source = builder.str;
+ if (regex_source_lang.match (source, 0, out info)) {
+ string lang_name = info.fetch (1).down ();
+ SourceCode.Language? lang = SourceCode.Language.from_string (lang_name);
+ code.language = lang;
+
+ if (lang == null) {
+ report_warning (source_token, "Unknown language `%s' in source code block
|[<!-- language=\"\"".printf (lang_name));
+ }
+
+ source = source.offset (source.index_of_char ('>') + 1);
+ } else {
+ code.language = (Highlighter.XmlScanner.is_xml (source))
+ ? SourceCode.Language.XML
+ : SourceCode.Language.C;
+ }
+ code.code = source;
Paragraph p = factory.create_paragraph ();
- p.content.add (src);
+ p.content.add (code);
if (current.type != TokenType.GTKDOC_SOURCE_CLOSE) {
this.report_unexpected_token (current, "|]");
diff --git a/src/libvaladoc/documentation/gtkdocmarkdownparser.vala
b/src/libvaladoc/documentation/gtkdocmarkdownparser.vala
index b5e5192..ff52e95 100644
--- a/src/libvaladoc/documentation/gtkdocmarkdownparser.vala
+++ b/src/libvaladoc/documentation/gtkdocmarkdownparser.vala
@@ -371,6 +371,10 @@ public class Valadoc.Gtkdoc.MarkdownParser : Object, ResourceLocator {
}
source = source.offset (source.index_of_char ('>') + 1);
+ } else {
+ code.language = (Highlighter.XmlScanner.is_xml (source))
+ ? SourceCode.Language.XML
+ : SourceCode.Language.C;
}
code.code = source;
diff --git a/src/libvaladoc/documentation/wikiscanner.vala b/src/libvaladoc/documentation/wikiscanner.vala
index d3e36c6..81f6ae8 100644
--- a/src/libvaladoc/documentation/wikiscanner.vala
+++ b/src/libvaladoc/documentation/wikiscanner.vala
@@ -117,20 +117,14 @@ public class Valadoc.WikiScanner : Object, Scanner {
_column++;
if (_skip == 0) {
if (_code_escape_mode) {
- switch (c) {
- case '}':
- if (get_next_char (1) == c && get_next_char (2) == c) {
- _code_escape_mode = false; // This is a temporary hack
- emit_token (TokenType.TRIPLE_CLOSED_BRACE);
- _skip = 2;
- } else {
- append_char (c);
- }
- return;
- default:
+ if (c == '}' && get_next_char (1) == '}' && get_next_char (2) == '}') {
+ _code_escape_mode = false; // This is a temporary hack
+ emit_token (TokenType.TRIPLE_CLOSED_BRACE);
+ _skip = 2;
+ } else {
append_char (c);
- return;
}
+ return;
} else if (_url_escape_mode) {
switch (c) {
// Reserved characters
diff --git a/src/libvaladoc/highlighter/codescanner.vala b/src/libvaladoc/highlighter/codescanner.vala
new file mode 100644
index 0000000..8b15ee7
--- /dev/null
+++ b/src/libvaladoc/highlighter/codescanner.vala
@@ -0,0 +1,572 @@
+/* codescanner.vala
+ *
+ * Copyright (C) 2015 Florian Brosch
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author:
+ * Florian Brosch <flo brosch gmail com>
+ */
+
+using GLib;
+
+
+/**
+ * A cheap scanner used to highlight C and Vala source code.
+ */
+public class Valadoc.Highlighter.CodeScanner : Object, Scanner {
+ private Gee.HashMap<string, CodeTokenType?> keywords;
+ private bool enable_string_templates;
+ private bool enabel_verbatim_string;
+ private bool enable_preprocessor_define;
+ private bool enable_preprocessor_include;
+ private bool enable_keyword_escape;
+
+
+ private Queue<CodeToken> token_queue = new Queue<CodeToken> ();
+ private unowned string content;
+ private unowned string pos;
+
+
+ public CodeScanner (string content, bool enable_string_templates, bool enabel_verbatim_string,
+ bool enable_preprocessor_define, bool enable_preprocessor_include, bool enable_keyword_escape,
+ Gee.HashMap<string, CodeTokenType?> keywords)
+ {
+ this.content = content;
+ this.pos = content;
+
+ this.enable_string_templates = enable_string_templates;
+ this.enabel_verbatim_string = enabel_verbatim_string;
+ this.enable_preprocessor_define = enable_preprocessor_define;
+ this.enable_preprocessor_include = enable_preprocessor_include;
+ this.enable_keyword_escape = enable_keyword_escape;
+
+ this.keywords = keywords;
+ }
+
+ public CodeToken next () {
+ if (!token_queue.is_empty ()) {
+ return token_queue.pop_head ();
+ }
+
+
+ unowned string start;
+
+ for (start = pos; pos[0] != '\0'; pos = pos.next_char ()) {
+ if (((char*) pos) == ((char*) content) || pos[0] == '\n') {
+ unowned string line_start = pos;
+
+ while (pos[0] == ' ' || pos[0] == '\t' || pos[0] == '\n') {
+ pos = pos.offset (1);
+ }
+
+ if (pos[0] == '\0') {
+ break;
+ } else if (enable_preprocessor_include && pos.has_prefix ("#include")) {
+ unowned string end = pos;
+ if (queue_c_include ()) {
+ return dispatch (start, end);
+ } else {
+ pos = line_start;
+ continue;
+ }
+ } else if (pos.has_prefix ("#if") || pos.has_prefix ("#else") ||
pos.has_prefix ("#elif") || pos.has_prefix ("#endif")
+ || (enable_preprocessor_define && (pos.has_prefix ("#defined") ||
pos.has_prefix ("#ifdef")))) {
+
+ unowned string end = pos;
+ queue_until ('\n', CodeTokenType.PREPROCESSOR);
+ return dispatch (start, end);
+ }
+ }
+
+ if (pos[0] == '\'') {
+ unowned string end = pos;
+ queue_string_literal ("\'");
+ return dispatch (start, end);
+ }
+
+ if (pos[0] == '"' || (enable_string_templates && pos[0] == '@' && pos[1] == '"')) {
+ unowned string end = pos;
+ if (enabel_verbatim_string && (pos.has_prefix ("\"\"\"") ||
(enable_string_templates && pos.has_prefix ("@\"\"\"")))) {
+ queue_string_literal ("\"\"\"");
+ } else {
+ queue_string_literal ("\"");
+ }
+ return dispatch (start, end);
+ }
+
+ if (pos[0] >= '0' && pos[0] <= '9') {
+ unowned string end = pos;
+ queue_numeric_literal ();
+ return dispatch (start, end);
+ }
+
+ if (pos.has_prefix ("/*")) {
+ unowned string end = pos;
+ queue_multiline_comment ();
+ return dispatch (start, end);
+ }
+
+ if (pos.has_prefix ("//")) {
+ unowned string end = pos;
+ queue_until ('\n', CodeTokenType.COMMENT);
+ return dispatch (start, end);
+ }
+
+ if ((((char*) pos) == ((char*) content) || !isidstartchar (pos[-1])) && isidstartchar
(pos[0])) {
+ unowned string end = pos;
+ if (queue_keyword ()) {
+ return dispatch (start, end);
+ } else {
+ continue;
+ }
+ }
+ }
+
+ token_queue.push_tail (new CodeToken (CodeTokenType.EOF, ""));
+ return dispatch (start, pos);
+ }
+
+ private bool queue_c_include () {
+ unowned string include_start = pos;
+ unowned string start = pos;
+ pos = pos.offset (8);
+
+ while (pos[0] == ' ' || pos[0] == '\t') {
+ pos = pos.offset (1);
+ }
+
+ char? end_char = null;
+ if (pos[0] == '"') {
+ end_char = '"';
+ } else if (pos[0] == '<') {
+ end_char = '>';
+ }
+
+ if (end_char != null) {
+ queue_token (start, pos, CodeTokenType.PREPROCESSOR);
+
+ unowned string literal_start = pos;
+ pos = pos.offset (1);
+
+ while (pos[0] != end_char && pos[0] != '\n' && pos[0] != '\0') {
+ pos = pos.offset (1);
+ }
+
+ if (pos[0] == end_char) {
+ pos = pos.offset (1);
+
+ queue_token (literal_start, pos, CodeTokenType.LITERAL);
+ start = pos;
+ } else {
+ pos = include_start;
+ token_queue.clear ();
+ return false;
+ }
+ }
+
+ while (pos[0] == ' ' || pos[0] == '\t') {
+ pos = pos.offset (1);
+ }
+
+ if (pos[0] == '\n' || pos[0] == '\0') {
+ queue_token (start, pos, CodeTokenType.PREPROCESSOR);
+ return true;
+ } else {
+ pos = include_start;
+ token_queue.clear ();
+ return false;
+ }
+ }
+
+ private bool queue_keyword () {
+ unowned string start = pos;
+ if (pos[0] == '@') {
+ pos = pos.offset (1);
+ }
+ while (isidchar (pos[0])) {
+ pos = pos.offset (1);
+ }
+
+ long length = start.pointer_to_offset (pos);
+ string word = start.substring (0, length);
+ CodeTokenType? token_type = keywords.get (word);
+ if (token_type == null) {
+ pos = start;
+ return false;
+ }
+
+ token_queue.push_tail (new CodeToken (token_type, word));
+ return true;
+ }
+
+ private void queue_multiline_comment () {
+ unowned string start = pos;
+ pos = pos.offset (2);
+
+ while (!(pos[0] == '*' && pos[1] == '/') && pos[0] != '\0') {
+ pos = pos.offset (1);
+ }
+
+ if (pos[0] != '\0') {
+ pos = pos.offset (2);
+ }
+
+ queue_token (start, pos, CodeTokenType.COMMENT);
+ }
+
+ private void queue_until (char end_char, CodeTokenType token_type) {
+ unowned string start = pos;
+ pos = pos.offset (1);
+
+ while (pos[0] != end_char && pos[0] != '\0') {
+ pos = pos.offset (1);
+ }
+
+ if (pos[0] != '\0' && pos[0] != '\n') {
+ pos = pos.offset (1);
+ }
+
+ queue_token (start, pos, token_type);
+ }
+
+ private void queue_string_literal (string end_chars) {
+ unowned string start = pos;
+ bool is_template = false;
+
+ if (pos[0] == '@') {
+ pos = pos.offset (end_chars.length + 1);
+ is_template = true;
+ } else {
+ pos = pos.offset (end_chars.length);
+ }
+
+ while (!pos.has_prefix (end_chars) && pos[0] != '\0') {
+ long skip = 0;
+
+ if ((pos[0] == '%' && has_printf_format_prefix (out skip))
+ || (pos[0] == '\\' && has_escape_prefix (out skip))
+ || (is_template && pos[0] == '$' && has_template_literal_prefix (out skip)))
+ {
+ queue_token (start, pos, CodeTokenType.LITERAL);
+
+ unowned string sub_start = pos;
+ pos = pos.offset (skip);
+ queue_token (sub_start, pos, CodeTokenType.ESCAPE);
+ start = pos;
+ } else {
+ pos = pos.offset (1);
+ }
+ }
+
+ if (pos[0] != '\0') {
+ pos = pos.offset (end_chars.length);
+ }
+
+ queue_token (start, pos, CodeTokenType.LITERAL);
+ }
+
+ private bool has_template_literal_prefix (out long skip) {
+ if (isidchar (pos[1])) {
+ skip = 1;
+ while (isidchar (pos[skip])) {
+ skip++;
+ }
+ return true;
+ }
+
+ if (pos[1] == '(') {
+ int level = 1;
+ skip = 2;
+
+ while (level > 0) {
+ switch (pos[skip]) {
+ case '(':
+ level++;
+ break;
+ case ')':
+ level--;
+ break;
+ case '\0':
+ skip = 0;
+ return false;
+ }
+ skip++;
+ }
+ return true;
+ }
+
+ skip = 0;
+ return false;
+ }
+
+ private bool has_escape_prefix (out long skip) {
+ switch (pos[1]) {
+ case 'a':
+ case 'b':
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ case 'v':
+ case '\\':
+ case '\'':
+ case '\"':
+ case '?':
+ skip = 2;
+ return true;
+
+ case 'x':
+ if (pos[2].isxdigit ()) {
+ for (skip = 2; pos[skip].isxdigit (); skip++) {
+ skip++;
+ }
+
+ skip++;
+ return true;
+ }
+
+ skip = 0;
+ return false;
+
+ default:
+ if (pos[1].isdigit ()) {
+ skip = 2;
+
+ if (pos[2].isdigit ()) {
+ skip++;
+
+ if (pos[3].isdigit ()) {
+ skip++;
+ }
+ }
+
+ return true;
+ }
+
+ skip = 0;
+ return false;
+ }
+ }
+
+ private bool has_printf_format_prefix (out long skip) {
+ // %[flag][min width][precision][length modifier][conversion specifier]
+ unowned string pos = this.pos;
+ unowned string start = pos;
+
+ // '%'
+ pos = pos.offset (1);
+
+ if (pos[0] == '%') {
+ pos = pos.offset (1);
+ skip = 2;
+ return true;
+ }
+
+
+ // flags:
+ while ("#0+- ".index_of_char (pos[0]) > 0) {
+ pos = pos.offset (1);
+ }
+
+ // min width:
+ while (pos[0].isdigit ()) {
+ pos = pos.offset (1);
+ }
+
+ // precision
+ if (pos[0] == '.' && pos[1].isdigit ()) {
+ pos = pos.offset (2);
+ while (pos[0].isdigit ()) {
+ pos = pos.offset (1);
+ }
+ }
+
+ // length:
+ switch (pos[0]) {
+ case 'h':
+ pos = pos.offset (1);
+ if (pos[0] == 'h') {
+ pos = pos.offset (1);
+ }
+ break;
+
+ case 'l':
+ pos = pos.offset (1);
+ if (pos[0] == 'l') {
+ pos = pos.offset (1);
+ }
+ break;
+
+ case 'j':
+ case 'z':
+ case 't':
+ case 'L':
+ pos = pos.offset (1);
+ break;
+ }
+
+ // conversion specifier:
+ switch (pos[0]) {
+ case 'd':
+ case 'i':
+ case 'u':
+ case 'o':
+ case 'x':
+ case 'X':
+ case 'f':
+ case 'F':
+ case 'e':
+ case 'E':
+ case 'g':
+ case 'G':
+ case 'a':
+ case 'A':
+ case 'c':
+ case 's':
+ case 'p':
+ case 'n':
+ pos = pos.offset (1);
+ break;
+
+ default:
+ skip = 0;
+ return false;
+ }
+
+ skip = start.pointer_to_offset (pos);
+ return true;
+ }
+
+ private enum NumericType {
+ INTEGER,
+ REAL,
+ NONE
+ }
+
+ // based on libvala
+ private void queue_numeric_literal () {
+ NumericType numeric_type = NumericType.INTEGER;
+ unowned string start = pos;
+
+
+ // integer part
+ if (pos[0] == '0' && pos[1] == 'x' && pos[2].isxdigit ()) {
+ // hexadecimal integer literal
+ pos = pos.offset (2);
+ while (pos[0].isxdigit ()) {
+ pos = pos.offset (1);
+ }
+ } else {
+ // decimal number
+ while (pos[0].isdigit ()) {
+ pos = pos.offset (1);
+ }
+ }
+
+
+ // fractional part
+ if (pos[0] == '.' && pos[1].isdigit ()) {
+ numeric_type = NumericType.REAL;
+ pos = pos.offset (1);
+ while (pos[0].isdigit ()) {
+ pos = pos.offset (1);
+ }
+ }
+
+
+ // exponent part
+ if (pos[0] == 'e' || pos[0] == 'E') {
+ numeric_type = NumericType.REAL;
+ pos = pos.offset (1);
+ if (pos[0] == '+' || pos[0] == '-') {
+ pos = pos.offset (1);
+ }
+ while (pos[0].isdigit ()) {
+ pos = pos.offset (1);
+ }
+ }
+
+
+ // type suffix
+ switch (pos[0]) {
+ case 'l':
+ case 'L':
+ if (numeric_type == NumericType.INTEGER) {
+ pos = pos.offset (1);
+ if (pos[0] == 'l' || pos[0] == 'L') {
+ pos = pos.offset (1);
+ }
+ }
+ break;
+
+ case 'u':
+ case 'U':
+ if (numeric_type == NumericType.INTEGER) {
+ pos = pos.offset (1);
+ if (pos[0] == 'l' || pos[0] == 'L') {
+ pos = pos.offset (1);
+ if (pos[0] == 'l' || pos[0] == 'L') {
+ pos = pos.offset (1);
+ }
+ }
+ }
+ break;
+
+ case 'f':
+ case 'F':
+ case 'd':
+ case 'D':
+ numeric_type = NumericType.REAL;
+ pos = pos.offset (1);
+ break;
+ }
+
+ if (pos[0].isalnum ()) {
+ numeric_type = NumericType.NONE;
+ }
+
+ queue_token (start, pos, (numeric_type != NumericType.NONE)
+ ? CodeTokenType.LITERAL
+ : CodeTokenType.PLAIN);
+ }
+
+ private CodeToken dispatch (string start, string end) {
+ assert (token_queue.is_empty () == false);
+
+ if (((char*) start) == ((char*) end)) {
+ return token_queue.pop_head ();
+ }
+
+ long length = start.pointer_to_offset (end);
+ string content = start.substring (0, length);
+ return new CodeToken (CodeTokenType.PLAIN, content);
+ }
+
+ private void queue_token (string start, string end, CodeTokenType token_type) {
+ long length = start.pointer_to_offset (end);
+ string content = start.substring (0, length);
+ token_queue.push_tail (new CodeToken (token_type, content));
+ }
+
+ private inline bool isidchar (char c) {
+ return c.isalnum () || c == '_';
+ }
+
+ private inline bool isidstartchar (char c) {
+ return c.isalnum () || c == '_' || (c == '@' && enable_keyword_escape);
+ }
+}
+
diff --git a/src/libvaladoc/highlighter/codetoken.vala b/src/libvaladoc/highlighter/codetoken.vala
new file mode 100644
index 0000000..1a02195
--- /dev/null
+++ b/src/libvaladoc/highlighter/codetoken.vala
@@ -0,0 +1,58 @@
+/* codetoken.vala
+ *
+ * Copyright (C) 2015 Florian Brosch
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author:
+ * Florian Brosch <flo brosch gmail com>
+ */
+
+
+public class Valadoc.Highlighter.CodeToken {
+ public CodeTokenType token_type { get; private set; }
+ public string content { get; private set;}
+
+ public CodeToken (CodeTokenType type, string content) {
+ this.token_type = type;
+ this.content = content;
+ }
+}
+
+
+public enum Valadoc.Highlighter.CodeTokenType {
+ XML_ESCAPE,
+ XML_ELEMENT,
+ XML_ATTRIBUTE,
+ XML_ATTRIBUTE_VALUE,
+ XML_COMMENT,
+ XML_CDATA,
+
+ PREPROCESSOR,
+ COMMENT,
+ KEYWORD,
+ LITERAL,
+ ESCAPE,
+ PLAIN,
+ TYPE,
+ EOF;
+
+ public unowned string to_string () {
+ EnumClass enumc = (EnumClass) typeof (CodeTokenType).class_ref ();
+ unowned EnumValue? eval = enumc.get_value (this);
+ return_val_if_fail (eval != null, null);
+ return eval.value_nick;
+ }
+}
diff --git a/src/libvaladoc/highlighter/highlighter.vala b/src/libvaladoc/highlighter/highlighter.vala
new file mode 100644
index 0000000..3c25780
--- /dev/null
+++ b/src/libvaladoc/highlighter/highlighter.vala
@@ -0,0 +1,366 @@
+/* codehighlighter.vala
+ *
+ * Copyright (C) 2015 Florian Brosch
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author:
+ * Florian Brosch <flo brosch gmail com>
+ */
+
+using GLib;
+using Valadoc.Content;
+
+
+public class Valadoc.Highlighter.Highlighter : Object {
+ private Gee.HashMap<string, CodeTokenType?> vala_keywords;
+ private Gee.HashMap<string, CodeTokenType?> c_keywords;
+
+
+ /**
+ * Used to highlight vala source code.
+ */
+ public Run highlight_vala (string source_code) {
+ if (vala_keywords == null) {
+ vala_keywords = new Gee.HashMap<string, CodeTokenType?> ();
+
+ // ** Types: **
+ vala_keywords.set ("string", CodeTokenType.TYPE);
+ vala_keywords.set ("bool", CodeTokenType.TYPE);
+ vala_keywords.set ("void", CodeTokenType.TYPE);
+
+ vala_keywords.set ("double", CodeTokenType.TYPE);
+ vala_keywords.set ("float", CodeTokenType.TYPE);
+
+ vala_keywords.set ("char", CodeTokenType.TYPE);
+ vala_keywords.set ("uchar", CodeTokenType.TYPE);
+ vala_keywords.set ("unichar", CodeTokenType.TYPE);
+
+ vala_keywords.set ("short", CodeTokenType.TYPE);
+ vala_keywords.set ("ushort", CodeTokenType.TYPE);
+
+ vala_keywords.set ("long", CodeTokenType.TYPE);
+ vala_keywords.set ("ulong", CodeTokenType.TYPE);
+
+ vala_keywords.set ("size_t", CodeTokenType.TYPE);
+ vala_keywords.set ("ssize_t", CodeTokenType.TYPE);
+
+ vala_keywords.set ("int", CodeTokenType.TYPE);
+ vala_keywords.set ("int8", CodeTokenType.TYPE);
+ vala_keywords.set ("int16", CodeTokenType.TYPE);
+ vala_keywords.set ("int32", CodeTokenType.TYPE);
+ vala_keywords.set ("int64", CodeTokenType.TYPE);
+
+ vala_keywords.set ("uint", CodeTokenType.TYPE);
+ vala_keywords.set ("uint8", CodeTokenType.TYPE);
+ vala_keywords.set ("uint16", CodeTokenType.TYPE);
+ vala_keywords.set ("uint32", CodeTokenType.TYPE);
+ vala_keywords.set ("uint64", CodeTokenType.TYPE);
+
+
+ // ** Literals: **
+ vala_keywords.set ("null", CodeTokenType.LITERAL);
+ vala_keywords.set ("true", CodeTokenType.LITERAL);
+ vala_keywords.set ("false", CodeTokenType.LITERAL);
+
+
+ // ** Keywords: **
+ vala_keywords.set ("return", CodeTokenType.KEYWORD);
+ vala_keywords.set ("lock", CodeTokenType.KEYWORD);
+ vala_keywords.set ("var", CodeTokenType.KEYWORD);
+ vala_keywords.set ("yield", CodeTokenType.KEYWORD);
+ vala_keywords.set ("global", CodeTokenType.KEYWORD);
+ vala_keywords.set ("construct", CodeTokenType.KEYWORD);
+
+ vala_keywords.set ("value", CodeTokenType.KEYWORD);
+ vala_keywords.set ("get", CodeTokenType.KEYWORD);
+ vala_keywords.set ("set", CodeTokenType.KEYWORD);
+
+ vala_keywords.set ("owned", CodeTokenType.KEYWORD);
+ vala_keywords.set ("unowned", CodeTokenType.KEYWORD);
+ vala_keywords.set ("const", CodeTokenType.KEYWORD);
+ vala_keywords.set ("weak", CodeTokenType.KEYWORD);
+ vala_keywords.set ("dynamic", CodeTokenType.KEYWORD);
+
+ vala_keywords.set ("out", CodeTokenType.KEYWORD);
+ vala_keywords.set ("ref", CodeTokenType.KEYWORD);
+
+ vala_keywords.set ("break", CodeTokenType.KEYWORD);
+ vala_keywords.set ("continue", CodeTokenType.KEYWORD);
+ vala_keywords.set ("return", CodeTokenType.KEYWORD);
+
+ vala_keywords.set ("if", CodeTokenType.KEYWORD);
+ vala_keywords.set ("else", CodeTokenType.KEYWORD);
+ vala_keywords.set ("switch", CodeTokenType.KEYWORD);
+ vala_keywords.set ("case", CodeTokenType.KEYWORD);
+ vala_keywords.set ("default", CodeTokenType.KEYWORD);
+
+ vala_keywords.set ("do", CodeTokenType.KEYWORD);
+ vala_keywords.set ("while", CodeTokenType.KEYWORD);
+ vala_keywords.set ("for", CodeTokenType.KEYWORD);
+ vala_keywords.set ("foreach", CodeTokenType.KEYWORD);
+ vala_keywords.set ("in", CodeTokenType.KEYWORD);
+
+ vala_keywords.set ("try", CodeTokenType.KEYWORD);
+ vala_keywords.set ("catch", CodeTokenType.KEYWORD);
+ vala_keywords.set ("finally", CodeTokenType.KEYWORD);
+ vala_keywords.set ("throw", CodeTokenType.KEYWORD);
+
+ vala_keywords.set ("class", CodeTokenType.KEYWORD);
+ vala_keywords.set ("interface", CodeTokenType.KEYWORD);
+ vala_keywords.set ("struct", CodeTokenType.KEYWORD);
+ vala_keywords.set ("enum", CodeTokenType.KEYWORD);
+ vala_keywords.set ("delegate", CodeTokenType.KEYWORD);
+ vala_keywords.set ("errordomain", CodeTokenType.KEYWORD);
+
+ vala_keywords.set ("abstract", CodeTokenType.KEYWORD);
+ vala_keywords.set ("virtual", CodeTokenType.KEYWORD);
+ vala_keywords.set ("override", CodeTokenType.KEYWORD);
+ vala_keywords.set ("signal", CodeTokenType.KEYWORD);
+ vala_keywords.set ("extern", CodeTokenType.KEYWORD);
+ vala_keywords.set ("static", CodeTokenType.KEYWORD);
+ vala_keywords.set ("async", CodeTokenType.KEYWORD);
+ vala_keywords.set ("inline", CodeTokenType.KEYWORD);
+ vala_keywords.set ("new", CodeTokenType.KEYWORD);
+
+ vala_keywords.set ("public", CodeTokenType.KEYWORD);
+ vala_keywords.set ("private", CodeTokenType.KEYWORD);
+ vala_keywords.set ("protected", CodeTokenType.KEYWORD);
+ vala_keywords.set ("internal", CodeTokenType.KEYWORD);
+
+ vala_keywords.set ("throws", CodeTokenType.KEYWORD);
+ vala_keywords.set ("requires", CodeTokenType.KEYWORD);
+ vala_keywords.set ("ensures", CodeTokenType.KEYWORD);
+ vala_keywords.set ("assert", CodeTokenType.KEYWORD);
+
+ vala_keywords.set ("namespace", CodeTokenType.KEYWORD);
+ vala_keywords.set ("using", CodeTokenType.KEYWORD);
+
+ vala_keywords.set ("as", CodeTokenType.KEYWORD);
+ vala_keywords.set ("is", CodeTokenType.KEYWORD);
+ vala_keywords.set ("in", CodeTokenType.KEYWORD);
+ vala_keywords.set ("new", CodeTokenType.KEYWORD);
+ vala_keywords.set ("delete", CodeTokenType.KEYWORD);
+ vala_keywords.set ("sizeof", CodeTokenType.KEYWORD);
+ vala_keywords.set ("typeof", CodeTokenType.KEYWORD);
+
+ vala_keywords.set ("this", CodeTokenType.KEYWORD);
+ vala_keywords.set ("base", CodeTokenType.KEYWORD);
+ }
+
+ bool enable_string_templates = true;
+ bool enable_preprocessor_define = false;
+ bool enable_preprocessor_include = false;
+ bool enable_keyword_escape = true;
+ bool enabel_verbatim_string = true;
+
+ CodeScanner scanner = new CodeScanner (source_code, enable_string_templates,
enabel_verbatim_string,
+ enable_preprocessor_define, enable_preprocessor_include, enable_keyword_escape,
+ vala_keywords);
+
+ return highlight_code (scanner);
+ }
+
+ /**
+ * Used to highlight C source code.
+ */
+ public Run highlight_c (string source_code) {
+ if (c_keywords == null) {
+ c_keywords = new Gee.HashMap<string, CodeTokenType?> ();
+
+ // ** Types: **
+ c_keywords.set ("auto", CodeTokenType.TYPE);
+ c_keywords.set ("char", CodeTokenType.TYPE);
+ c_keywords.set ("const", CodeTokenType.TYPE);
+ c_keywords.set ("double", CodeTokenType.TYPE);
+ c_keywords.set ("extern", CodeTokenType.TYPE);
+ c_keywords.set ("int", CodeTokenType.TYPE);
+ c_keywords.set ("float", CodeTokenType.TYPE);
+ c_keywords.set ("long", CodeTokenType.TYPE);
+ c_keywords.set ("register", CodeTokenType.TYPE);
+ c_keywords.set ("short", CodeTokenType.TYPE);
+ c_keywords.set ("signed", CodeTokenType.TYPE);
+ c_keywords.set ("static", CodeTokenType.TYPE);
+ c_keywords.set ("unsigned", CodeTokenType.TYPE);
+ c_keywords.set ("void", CodeTokenType.TYPE);
+ c_keywords.set ("volatile", CodeTokenType.TYPE);
+
+ c_keywords.set ("gboolean", CodeTokenType.TYPE);
+ c_keywords.set ("gpointer", CodeTokenType.TYPE);
+ c_keywords.set ("gconstpointer", CodeTokenType.TYPE);
+ c_keywords.set ("gchar", CodeTokenType.TYPE);
+ c_keywords.set ("guchar", CodeTokenType.TYPE);
+ c_keywords.set ("gint", CodeTokenType.TYPE);
+ c_keywords.set ("guint", CodeTokenType.TYPE);
+ c_keywords.set ("gshort", CodeTokenType.TYPE);
+ c_keywords.set ("gushort", CodeTokenType.TYPE);
+ c_keywords.set ("glong", CodeTokenType.TYPE);
+ c_keywords.set ("gulong", CodeTokenType.TYPE);
+ c_keywords.set ("gint8", CodeTokenType.TYPE);
+ c_keywords.set ("guint8", CodeTokenType.TYPE);
+ c_keywords.set ("gint16", CodeTokenType.TYPE);
+ c_keywords.set ("guint16", CodeTokenType.TYPE);
+ c_keywords.set ("gint32", CodeTokenType.TYPE);
+ c_keywords.set ("guint32", CodeTokenType.TYPE);
+ c_keywords.set ("gint64", CodeTokenType.TYPE);
+ c_keywords.set ("guint64", CodeTokenType.TYPE);
+ c_keywords.set ("gfloat", CodeTokenType.TYPE);
+ c_keywords.set ("gdouble", CodeTokenType.TYPE);
+ c_keywords.set ("gsize", CodeTokenType.TYPE);
+ c_keywords.set ("gssize", CodeTokenType.TYPE);
+ c_keywords.set ("goffset", CodeTokenType.TYPE);
+ c_keywords.set ("gintptr", CodeTokenType.TYPE);
+ c_keywords.set ("guintptr", CodeTokenType.TYPE);
+
+
+ // ** Literals: **
+ c_keywords.set ("NULL", CodeTokenType.LITERAL);
+ c_keywords.set ("TRUE", CodeTokenType.LITERAL);
+ c_keywords.set ("FALSE", CodeTokenType.LITERAL);
+
+
+ // ** Keywords: **
+ c_keywords.set ("break", CodeTokenType.KEYWORD);
+ c_keywords.set ("case", CodeTokenType.KEYWORD);
+ c_keywords.set ("continue", CodeTokenType.KEYWORD);
+ c_keywords.set ("default", CodeTokenType.KEYWORD);
+ c_keywords.set ("do", CodeTokenType.KEYWORD);
+ c_keywords.set ("else", CodeTokenType.KEYWORD);
+ c_keywords.set ("enum", CodeTokenType.KEYWORD);
+ c_keywords.set ("for", CodeTokenType.KEYWORD);
+ c_keywords.set ("goto", CodeTokenType.KEYWORD);
+ c_keywords.set ("if", CodeTokenType.KEYWORD);
+ c_keywords.set ("return", CodeTokenType.KEYWORD);
+ c_keywords.set ("sizeof", CodeTokenType.KEYWORD);
+ c_keywords.set ("struct", CodeTokenType.KEYWORD);
+ c_keywords.set ("switch", CodeTokenType.KEYWORD);
+ c_keywords.set ("typedef", CodeTokenType.KEYWORD);
+ c_keywords.set ("union", CodeTokenType.KEYWORD);
+ c_keywords.set ("while", CodeTokenType.KEYWORD);
+ c_keywords.set ("assert", CodeTokenType.KEYWORD);
+ }
+
+ bool enable_string_templates = false;
+ bool enable_preprocessor_define = true;
+ bool enable_preprocessor_include = true;
+ bool enable_keyword_escape = false;
+ bool enabel_verbatim_string = false;
+
+ CodeScanner scanner = new CodeScanner (source_code, enable_string_templates,
enabel_verbatim_string,
+ enable_preprocessor_define, enable_preprocessor_include, enable_keyword_escape,
+ c_keywords);
+
+ return highlight_code (scanner);
+ }
+
+ /**
+ * Used to highlight C source code.
+ */
+ public Run highlight_xml (string source_code) {
+ XmlScanner scanner = new XmlScanner (source_code);
+ return highlight_code (scanner);
+ }
+
+ /**
+ * Used to highlight source code.
+ */
+ private Run highlight_code (Scanner scanner) {
+ Run code = new Run (Run.Style.MONOSPACED);
+
+ for (CodeToken token = scanner.next (); token.token_type != CodeTokenType.EOF; token =
scanner.next ()) {
+ switch (token.token_type) {
+ case CodeTokenType.PREPROCESSOR:
+ Run run = new Run (Run.Style.LANG_PREPROCESSOR);
+ run.content.add (new Text (token.content));
+ code.content.add (run);
+ break;
+
+ case CodeTokenType.COMMENT:
+ Run run = new Run (Run.Style.LANG_COMMENT);
+ run.content.add (new Text (token.content));
+ code.content.add (run);
+ break;
+
+ case CodeTokenType.KEYWORD:
+ Run run = new Run (Run.Style.LANG_KEYWORD);
+ run.content.add (new Text (token.content));
+ code.content.add (run);
+ break;
+
+ case CodeTokenType.LITERAL:
+ Run run = new Run (Run.Style.LANG_LITERAL);
+ run.content.add (new Text (token.content));
+ code.content.add (run);
+ break;
+
+ case CodeTokenType.TYPE:
+ Run run = new Run (Run.Style.LANG_BASIC_TYPE);
+ run.content.add (new Text (token.content));
+ code.content.add (run);
+ break;
+
+ case CodeTokenType.ESCAPE:
+ Run run = new Run (Run.Style.LANG_ESCAPE);
+ run.content.add (new Text (token.content));
+ code.content.add (run);
+ break;
+
+ case CodeTokenType.XML_ESCAPE:
+ Run run = new Run (Run.Style.XML_ESCAPE);
+ run.content.add (new Text (token.content));
+ code.content.add (run);
+ break;
+
+ case CodeTokenType.XML_ELEMENT:
+ Run run = new Run (Run.Style.XML_ELEMENT);
+ run.content.add (new Text (token.content));
+ code.content.add (run);
+ break;
+
+ case CodeTokenType.XML_ATTRIBUTE:
+ Run run = new Run (Run.Style.XML_ATTRIBUTE);
+ run.content.add (new Text (token.content));
+ code.content.add (run);
+ break;
+
+ case CodeTokenType.XML_ATTRIBUTE_VALUE:
+ Run run = new Run (Run.Style.XML_ATTRIBUTE_VALUE);
+ run.content.add (new Text (token.content));
+ code.content.add (run);
+ break;
+
+ case CodeTokenType.XML_COMMENT:
+ Run run = new Run (Run.Style.XML_COMMENT);
+ run.content.add (new Text (token.content));
+ code.content.add (run);
+ break;
+
+ case CodeTokenType.XML_CDATA:
+ Run run = new Run (Run.Style.XML_CDATA);
+ run.content.add (new Text (token.content));
+ code.content.add (run);
+ break;
+
+ default:
+ code.content.add (new Text (token.content));
+ break;
+ }
+ }
+
+ return code;
+ }
+}
+
+
diff --git a/src/libvaladoc/highlighter/scanner.vala b/src/libvaladoc/highlighter/scanner.vala
new file mode 100644
index 0000000..20eedcf
--- /dev/null
+++ b/src/libvaladoc/highlighter/scanner.vala
@@ -0,0 +1,32 @@
+/* scanner.vala
+ *
+ * Copyright (C) 2015 Florian Brosch
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author:
+ * Florian Brosch <flo brosch gmail com>
+ */
+
+using GLib;
+
+
+/**
+ * Scanner interface used to highlight source code.
+ */
+public interface Valadoc.Highlighter.Scanner : Object {
+
+ public abstract CodeToken next ();
+}
diff --git a/src/libvaladoc/highlighter/xmlscanner.vala b/src/libvaladoc/highlighter/xmlscanner.vala
new file mode 100644
index 0000000..38b87c5
--- /dev/null
+++ b/src/libvaladoc/highlighter/xmlscanner.vala
@@ -0,0 +1,374 @@
+/* xmlscanner.vala
+ *
+ * Copyright (C) 2015 Florian Brosch
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author:
+ * Florian Brosch <flo brosch gmail com>
+ */
+
+using GLib;
+
+
+/**
+ * A cheap scanner used to highlight XML.
+ */
+public class Valadoc.Highlighter.XmlScanner : Object, Scanner {
+ private Queue<CodeToken> token_queue = new Queue<CodeToken> ();
+ private unowned string content;
+ private unowned string pos;
+
+
+ public XmlScanner (string content) {
+ this.content = content;
+ this.pos = content;
+ }
+
+ public CodeToken next () {
+ if (!token_queue.is_empty ()) {
+ return token_queue.pop_head ();
+ }
+
+ unowned string start;
+
+ for (start = pos; pos[0] != '\0'; pos = pos.next_char ()) {
+ if (pos[0] == '&') {
+ unowned string begin = pos;
+ if (queue_escape ()) {
+ return dispatch (start, begin);
+ }
+ } else if (pos[0] == '<') {
+ if (pos[1] == '/') {
+ unowned string end = pos;
+ if (queue_end_element ()) {
+ return dispatch (start, end);
+ }
+ } else if (pos[1] == '!' && pos[2] == '-' && pos[3] == '-') {
+ unowned string end = pos;
+ if (queue_comment ()) {
+ return dispatch (start, end);
+ }
+ } else if (pos[1] == '!' && pos[2] == '[' && pos[3] == 'C' && pos[4] == 'D'
&& pos[5] == 'A' && pos[6] == 'T' && pos[7] == 'A' && pos[8] == '[') {
+ unowned string end = pos;
+ pos = pos.offset (9);
+ token_queue.push_tail (new CodeToken (CodeTokenType.XML_CDATA,
"<![CDATA["));
+ return dispatch (start, end);
+ } else {
+ unowned string end = pos;
+ if (queue_start_element (start, pos[1] == '?')) {
+ return dispatch (start, end);
+ } else {
+ continue;
+ }
+ }
+ } else if (pos[0] == ']' && pos[1] == ']' && pos[2] == '>') {
+ unowned string end = pos;
+ pos = pos.offset (3);
+ token_queue.push_tail (new CodeToken (CodeTokenType.XML_CDATA, "]]>"));
+ return dispatch (start, end);
+ }
+ }
+
+ token_queue.push_tail (new CodeToken (CodeTokenType.EOF, ""));
+ return dispatch (start, pos);
+ }
+
+ private bool queue_start_element (string dispatch_start, bool xml_decl) {
+ assert (token_queue.is_empty ());
+
+ unowned string element_start = pos;
+ if (xml_decl) {
+ pos = pos.offset (2);
+ } else {
+ pos = pos.offset (1);
+ }
+
+ skip_optional_spaces (ref pos);
+
+ if (skip_id (ref pos) == false) {
+ token_queue.clear ();
+ pos = element_start;
+ return false;
+ }
+
+ skip_optional_spaces (ref pos);
+
+ queue_token (element_start, pos, CodeTokenType.XML_ELEMENT);
+
+ if (queue_attributes () == false) {
+ token_queue.clear ();
+ pos = element_start;
+ return false;
+ }
+
+ unowned string element_end_start = pos;
+
+ if (!xml_decl && pos[0] == '>') {
+ pos = pos.offset (1);
+ } else if (!xml_decl && pos[0] == '/' && pos[1] == '>') {
+ pos = pos.offset (2);
+ } else if (xml_decl && pos[0] == '?' && pos[1] == '>') {
+ pos = pos.offset (2);
+ } else {
+ token_queue.clear ();
+ pos = element_start;
+ return false;
+ }
+
+ queue_token (element_end_start, pos, CodeTokenType.XML_ELEMENT);
+ return true;
+ }
+
+ private bool queue_attributes () {
+ while (is_id_char (pos[0])) {
+ unowned string begin = pos;
+
+ if (skip_id (ref pos) == false) {
+ return false;
+ }
+
+ skip_optional_spaces (ref pos);
+
+ if (pos[0] == '=') {
+ pos = pos.offset (1);
+ } else {
+ return false;
+ }
+
+ skip_optional_spaces (ref pos);
+
+ queue_token (begin, pos, CodeTokenType.XML_ATTRIBUTE);
+ begin = pos;
+
+ if (pos[0] == '"') {
+ pos = pos.offset (1);
+ } else {
+ return false;
+ }
+
+ while (pos[0] != '"' && pos[0] != '\0') {
+ pos = pos.offset (1);
+ }
+
+ if (pos[0] == '"') {
+ pos = pos.offset (1);
+ } else {
+ return false;
+ }
+
+ skip_optional_spaces (ref pos);
+
+ queue_token (begin, pos, CodeTokenType.XML_ATTRIBUTE_VALUE);
+ }
+
+ return true;
+ }
+
+ private bool queue_end_element () {
+ unowned string start = pos;
+ pos = pos.offset (2);
+
+ skip_optional_spaces (ref pos);
+
+ if (skip_id (ref pos) == false) {
+ pos = start;
+ return false;
+ }
+
+ skip_optional_spaces (ref pos);
+
+ if (pos[0] == '>') {
+ pos = pos.offset (1);
+ } else {
+ pos = start;
+ return false;
+ }
+
+ queue_token (start, pos, CodeTokenType.XML_ELEMENT);
+ return true;
+ }
+
+ private bool queue_escape () {
+ unowned string start = pos;
+ pos = pos.offset (1);
+
+ if (skip_id (ref pos) == false) {
+ pos = start;
+ return false;
+ }
+
+ if (pos[0] == ';') {
+ pos = pos.offset (1);
+ } else {
+ pos = start;
+ return false;
+ }
+
+ queue_token (start, pos, CodeTokenType.XML_ESCAPE);
+ return true;
+ }
+
+ private bool queue_comment () {
+ unowned string start = pos;
+ pos = pos.offset (4);
+
+ while (pos[0] != '>' && pos[0] != '\0') {
+ pos = pos.offset (1);
+ }
+
+ if (pos[0] == '>') {
+ pos = pos.offset (1);
+ } else {
+ pos = start;
+ return false;
+ }
+
+ queue_token (start, pos, CodeTokenType.XML_COMMENT);
+ return true;
+ }
+
+ private static bool skip_id (ref unowned string pos) {
+ bool has_next_segment = true;
+ bool has_id = false;
+
+ while (has_next_segment) {
+ has_id = false;
+
+ while (is_id_char (pos[0])) {
+ pos = pos.offset (1);
+ has_id = true;
+ }
+
+ if (pos[0] == ':' && has_id) {
+ has_next_segment = true;
+ pos = pos.offset (1);
+ } else {
+ has_next_segment = false;
+ }
+ }
+
+ return has_id;
+ }
+
+ private static bool skip_optional_spaces (ref unowned string pos) {
+ bool skipped = false;
+
+ while (pos[0].isspace ()) {
+ pos = pos.offset (1);
+ skipped = true;
+ }
+
+ return skipped;
+ }
+
+ private CodeToken dispatch (string start, string end) {
+ assert (token_queue.is_empty () == false);
+
+ if (((char*) start) == ((char*) end)) {
+ return token_queue.pop_head ();
+ }
+
+ long length = start.pointer_to_offset (end);
+ string content = start.substring (0, length);
+ return new CodeToken (CodeTokenType.PLAIN, content);
+ }
+
+ private void queue_token (string start, string end, CodeTokenType token_type) {
+ long length = start.pointer_to_offset (end);
+ string content = start.substring (0, length);
+ token_queue.push_tail (new CodeToken (token_type, content));
+ }
+
+ private static inline bool is_id_char (char c) {
+ return c.isalnum () || c == '_' || c == '-';
+ }
+
+ internal static bool is_xml (string source) {
+ unowned string pos = source;
+
+ skip_optional_spaces (ref pos);
+
+ if (pos[0] == '<') {
+ // Comment:
+ if (pos.has_prefix ("<!--")) {
+ return true;
+ }
+
+ // CDATA:
+ if (pos.has_prefix ("<![CDATA[")) {
+ return true;
+ }
+
+
+ // Start Tag:
+ bool proc_instr = false;
+ pos = pos.offset (1);
+
+ if (pos[0] == '?') {
+ pos = pos.offset (1);
+ proc_instr = true;
+ }
+
+ // ID:
+ if (skip_id (ref pos) == false) {
+ return false;
+ }
+
+ skip_optional_spaces (ref pos);
+
+ while (skip_id (ref pos)) {
+ if (pos[0] == '=') {
+ pos = pos.offset (1);
+ } else {
+ return false;
+ }
+
+ skip_optional_spaces (ref pos);
+
+ if (pos[0] == '"') {
+ pos = pos.offset (1);
+ } else {
+ return false;
+ }
+
+ while (pos[0] != '\0' && pos[0] != '\n' && pos[0] != '"') {
+ pos = pos.offset (1);
+ }
+
+ if (pos[0] == '"') {
+ pos = pos.offset (1);
+ } else {
+ return false;
+ }
+
+ skip_optional_spaces (ref pos);
+ }
+
+ if (proc_instr && pos[0] == '?' && pos[1] == '>') {
+ return true;
+ }
+
+ if (!proc_instr && (pos[0] == '>' || (pos[0] == '/' && pos[1] == '>'))) {
+ return true;
+ }
+
+ return false;
+ } else {
+ return false;
+ }
+ }
+}
+
diff --git a/src/libvaladoc/html/htmlrenderer.vala b/src/libvaladoc/html/htmlrenderer.vala
index 84ba3f9..66fc22b 100644
--- a/src/libvaladoc/html/htmlrenderer.vala
+++ b/src/libvaladoc/html/htmlrenderer.vala
@@ -481,9 +481,13 @@ public class Valadoc.Html.HtmlRenderer : ContentRenderer {
tag = "span";
css_type = "main_keyword";
break;
+ case Run.Style.LANG_ESCAPE:
+ tag = "span";
+ css_type = "main_escape";
+ break;
case Run.Style.LANG_LITERAL:
tag = "span";
- css_type = "main_optional_parameter";
+ css_type = "main_literal";
break;
case Run.Style.LANG_BASIC_TYPE:
tag = "span";
@@ -493,6 +497,44 @@ public class Valadoc.Html.HtmlRenderer : ContentRenderer {
tag = "span";
css_type = "main_type";
break;
+ case Run.Style.LANG_COMMENT:
+ tag = "span";
+ css_type = "main_comment";
+ break;
+ case Run.Style.LANG_PREPROCESSOR:
+ tag = "span";
+ css_type = "main_preprocessor";
+ break;
+
+ case Run.Style.XML_ESCAPE:
+ tag = "span";
+ css_type = "xml_escape";
+ break;
+
+ case Run.Style.XML_ELEMENT:
+ tag = "span";
+ css_type = "xml_element";
+ break;
+
+ case Run.Style.XML_ATTRIBUTE:
+ tag = "span";
+ css_type = "xml_attribute";
+ break;
+
+ case Run.Style.XML_ATTRIBUTE_VALUE:
+ tag = "span";
+ css_type = "xml_attribute_value";
+ break;
+
+ case Run.Style.XML_COMMENT:
+ tag = "span";
+ css_type = "xml_comment";
+ break;
+
+ case Run.Style.XML_CDATA:
+ tag = "span";
+ css_type = "xml_cdata";
+ break;
}
if (tag != null) {
writer.start_tag (tag, {"class", css_type});
@@ -506,7 +548,7 @@ public class Valadoc.Html.HtmlRenderer : ContentRenderer {
public override void visit_source_code (SourceCode element) {
writer.set_wrap (false);
writer.start_tag ("pre", {"class", "main_source"});
- write_string (element.code);
+ element.accept_children (this);
writer.end_tag ("pre");
writer.set_wrap (true);
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]