diff --git a/aegisub/build/libaegisub/libaegisub.vcxproj b/aegisub/build/libaegisub/libaegisub.vcxproj
index 870e9ffdf..5600a1995 100644
--- a/aegisub/build/libaegisub/libaegisub.vcxproj
+++ b/aegisub/build/libaegisub/libaegisub.vcxproj
@@ -92,6 +92,7 @@
+
@@ -123,6 +124,7 @@
+
diff --git a/aegisub/build/libaegisub/libaegisub.vcxproj.filters b/aegisub/build/libaegisub/libaegisub.vcxproj.filters
index 04d67508d..4539c7db6 100644
--- a/aegisub/build/libaegisub/libaegisub.vcxproj.filters
+++ b/aegisub/build/libaegisub/libaegisub.vcxproj.filters
@@ -137,6 +137,9 @@
Header Files
+
+ Header Files
+
@@ -220,6 +223,9 @@
Source Files\Common
+
+ Source Files\Common
+
diff --git a/aegisub/libaegisub/Makefile b/aegisub/libaegisub/Makefile
index 49e9c9191..9e46d425b 100644
--- a/aegisub/libaegisub/Makefile
+++ b/aegisub/libaegisub/Makefile
@@ -20,6 +20,7 @@ SRC += \
common/cajun/elements.cpp \
common/cajun/reader.cpp \
common/cajun/writer.cpp \
+ common/calltip_provider.cpp \
common/charset.cpp \
common/charset_6937.cpp \
common/charset_conv.cpp \
diff --git a/aegisub/libaegisub/ass/dialogue_parser.cpp b/aegisub/libaegisub/ass/dialogue_parser.cpp
index b0010e3c2..14ef7e863 100644
--- a/aegisub/libaegisub/ass/dialogue_parser.cpp
+++ b/aegisub/libaegisub/ass/dialogue_parser.cpp
@@ -59,7 +59,8 @@ class SyntaxHighlighter {
char *outptr = (char *)&chr;
size_t outlen = sizeof chr;
- if (iconv(utf8_to_utf32, &inptr, &inlen, &outptr, &outlen) != 1)
+ iconv(utf8_to_utf32, &inptr, &inlen, &outptr, &outlen);
+ if (outlen != 0)
return 0;
char_len = len - inlen;
@@ -105,7 +106,7 @@ public:
SyntaxHighlighter(std::string const& text, agi::SpellChecker *spellchecker)
: text(text)
, spellchecker(spellchecker)
- , utf8_to_utf32(iconv_open("utf-32", "utf-8"), iconv_close)
+ , utf8_to_utf32(iconv_open("utf-32le", "utf-8"), iconv_close)
{ }
TokenVec Highlight(TokenVec const& tokens, bool template_line) {
diff --git a/aegisub/libaegisub/common/calltip_provider.cpp b/aegisub/libaegisub/common/calltip_provider.cpp
new file mode 100644
index 000000000..b3272e688
--- /dev/null
+++ b/aegisub/libaegisub/common/calltip_provider.cpp
@@ -0,0 +1,186 @@
+// Copyright (c) 2012, Thomas Goyne
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+#include "../config.h"
+
+#include "libaegisub/calltip_provider.h"
+
+#include "libaegisub/ass/dialogue_parser.h"
+
+#include
+
+namespace {
+ struct proto_lit {
+ const char *name;
+ bool has_parens;
+ const char *args;
+ };
+
+ proto_lit calltip_protos[] = {
+ { "move", true, "X1\0Y1\0X2\0Y2\0" },
+ { "move", true, "X1\0Y1\0X2\0Y2\0Start Time\0End Time\0" },
+ { "fn", false, "Font Name\0" },
+ { "bord", false, "Width\0" },
+ { "xbord", false, "Width\0" },
+ { "ybord", false, "Width\0" },
+ { "shad", false, "Depth\0" },
+ { "xshad", false, "Depth\0" },
+ { "yshad", false, "Depth\0" },
+ { "be", false, "Strength\0" },
+ { "blur", false, "Strength\0" },
+ { "fscx", false, "Scale\0" },
+ { "fscy", false, "Scale\0" },
+ { "fsp", false, "Spacing\0" },
+ { "fs", false, "Font Size\0" },
+ { "fe", false, "Encoding\0" },
+ { "frx", false, "Angle\0" },
+ { "fry", false, "Angle\0" },
+ { "frz", false, "Angle\0" },
+ { "fr", false, "Angle\0" },
+ { "pbo", false, "Offset\0" },
+ { "clip", true, "Command\0" },
+ { "clip", true, "Scale\0Command\0" },
+ { "clip", true, "X1\0Y1\0X2\0Y2\0" },
+ { "iclip", true, "Command\0" },
+ { "iclip", true, "Scale\0Command\0" },
+ { "iclip", true, "X1\0Y1\0X2\0Y2\0" },
+ { "t", true, "Acceleration\0Tags\0" },
+ { "t", true, "Start Time\0End Time\0Tags\0" },
+ { "t", true, "Start Time\0End Time\0Acceleration\0Tags\0" },
+ { "pos", true, "X\0Y\0" },
+ { "p", false, "Exponent\0" },
+ { "org", true, "X\0Y\0" },
+ { "fade", true, "Start Alpha\0Middle Alpha\0End Alpha\0Start In\0End In\0Start Out\0End Out\0" },
+ { "fad", true, "Start Time\0End Time\0" },
+ { "c", false, "Colour\0" },
+ { "1c", false, "Colour\0" },
+ { "2c", false, "Colour\0" },
+ { "3c", false, "Colour\0" },
+ { "4c", false, "Colour\0" },
+ { "alpha", false, "Alpha\0" },
+ { "1a", false, "Alpha\0" },
+ { "2a", false, "Alpha\0" },
+ { "3a", false, "Alpha\0" },
+ { "4a", false, "Alpha\0" },
+ { "an", false, "Alignment\0" },
+ { "a", false, "Alignment\0" },
+ { "b", false, "Weight\0" },
+ { "i", false, "1/0\0" },
+ { "u", false, "1/0\0" },
+ { "s", false, "1/0\0" },
+ { "kf", false, "Duration\0" },
+ { "ko", false, "Duration\0" },
+ { "k", false, "Duration\0" },
+ { "K", false, "Duration\0" },
+ { "q", false, "Wrap Style\0" },
+ { "r", false, "Style\0" },
+ { "fax", false, "Factor\0" },
+ { "fay", false, "Factor\0" }
+ };
+}
+
+namespace agi {
+CalltipProvider::CalltipProvider() {
+ for (auto proto : calltip_protos) {
+ CalltipProto p;
+ std::string tag_name = proto.name;
+ p.text = '\\' + tag_name;
+ if (proto.has_parens)
+ p.text += '(';
+
+ for (const char *arg = proto.args; *arg; ) {
+ size_t start = p.text.size();
+ p.text += arg;
+ size_t end = p.text.size();
+ if (proto.has_parens)
+ p.text += ',';
+
+ arg += end - start + 1;
+ p.args.emplace_back(start, end);
+ }
+
+ // replace trailing comma
+ if (proto.has_parens)
+ p.text.back() = ')';
+
+ protos.emplace(std::move(tag_name), std::move(p));
+ }
+}
+
+Calltip CalltipProvider::GetCalltip(std::vector const& tokens, std::string const& text, size_t pos) {
+ namespace dt = ass::DialogueTokenType;
+
+ Calltip ret = { "", 0, 0, 0 };
+
+ size_t idx = 0;
+ size_t tag_name_idx = 0;
+ size_t commas = 0;
+ for (; idx < tokens.size() && pos >= tokens[idx].length; ++idx) {
+ switch (tokens[idx].type) {
+ case dt::COMMENT:
+ case dt::OVR_END:
+ tag_name_idx = 0;
+ break;
+ case dt::TAG_NAME:
+ tag_name_idx = idx;
+ commas = 0;
+ break;
+ case dt::ARG_SEP:
+ ++commas;
+ break;
+ default: break;
+ }
+ pos -= tokens[idx].length;
+ }
+
+ // Either didn't hit a tag or the override block ended before reaching the
+ // current position
+ if (tag_name_idx == 0)
+ return ret;
+
+ // Find the prototype for this tag
+ size_t tag_name_start = 0;
+ for (size_t i = 0; i < tag_name_idx; ++i)
+ tag_name_start += tokens[i].length;
+ auto it = protos.equal_range(text.substr(tag_name_start, tokens[tag_name_idx].length));
+
+ // If there's multiple overloads, check how many total arguments we have
+ // and pick the one with the least args >= current arg count
+ if (distance(it.first, it.second) > 1) {
+ size_t args = commas + 1;
+ for (size_t i = idx; i < tokens.size(); ++i) {
+ int type = tokens[i].type;
+ if (type == dt::ARG_SEP)
+ ++args;
+ else if (type != dt::ARG && type != dt::WHITESPACE)
+ break;
+ }
+
+ while (it.first != it.second && args > it.first->second.args.size())
+ ++it.first;
+ }
+
+ // Unknown tag or too many arguments
+ if (it.first == it.second || it.first->second.args.size() <= commas)
+ return ret;
+
+ ret.text = it.first->second.text;
+ ret.highlight_start = it.first->second.args[commas].first;
+ ret.highlight_end = it.first->second.args[commas].second;
+ ret.tag_position = tag_name_start;
+ return ret;
+}
+}
diff --git a/aegisub/libaegisub/include/libaegisub/calltip_provider.h b/aegisub/libaegisub/include/libaegisub/calltip_provider.h
new file mode 100644
index 000000000..af7ab734e
--- /dev/null
+++ b/aegisub/libaegisub/include/libaegisub/calltip_provider.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2012, Thomas Goyne
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+#ifndef LAGI_PRE
+#include