2016-03-06 02:37:20 +01:00
|
|
|
#include "json_ui.hh"
|
|
|
|
|
|
|
|
#include "display_buffer.hh"
|
2017-08-29 10:23:03 +02:00
|
|
|
#include "event_manager.hh"
|
2016-11-29 00:53:50 +01:00
|
|
|
#include "exception.hh"
|
2016-03-06 02:37:20 +01:00
|
|
|
#include "file.hh"
|
2017-08-29 10:23:03 +02:00
|
|
|
#include "keys.hh"
|
|
|
|
#include "ranges.hh"
|
2017-10-09 16:12:42 +02:00
|
|
|
#include "string_utils.hh"
|
2016-03-06 02:37:20 +01:00
|
|
|
#include "unit_tests.hh"
|
2017-08-29 10:23:03 +02:00
|
|
|
#include "value.hh"
|
2016-03-06 02:37:20 +01:00
|
|
|
|
Fix build on FreeBSD
file.cc:390:21: error: use of undeclared identifier 'rename'; did you mean 'devname'?
if (replace and rename(temp_filename, zfilename) != 0)
^~~~~~
devname
/usr/include/stdlib.h:277:7: note: 'devname' declared here
char *devname(__dev_t, __mode_t);
^
file.cc:390:28: error: cannot initialize a parameter of type '__dev_t' (aka 'unsigned long') with an lvalue of type 'char [1024]'
if (replace and rename(temp_filename, zfilename) != 0)
^~~~~~~~~~~~~
/usr/include/stdlib.h:277:22: note: passing argument to parameter here
char *devname(__dev_t, __mode_t);
^
2 errors generated.
---
highlighters.cc:1110:13: error: use of undeclared identifier 'snprintf'; did you mean 'vswprintf'?
snprintf(buffer, 16, format, std::abs(line_to_format));
^~~~~~~~
vswprintf
/usr/include/wchar.h:139:5: note: 'vswprintf' declared here
int vswprintf(wchar_t * __restrict, size_t n, const wchar_t * __restrict,
^
highlighters.cc:1110:22: error: cannot initialize a parameter of type 'wchar_t *' with an lvalue of type 'char [16]'
snprintf(buffer, 16, format, std::abs(line_to_format));
^~~~~~
/usr/include/wchar.h:139:35: note: passing argument to parameter here
int vswprintf(wchar_t * __restrict, size_t n, const wchar_t * __restrict,
^
2 errors generated.
---
json_ui.cc:60:13: error: use of undeclared identifier 'sprintf'; did you mean 'swprintf'?
sprintf(buf, "\\u%04x", *next);
^~~~~~~
swprintf
/usr/include/wchar.h:133:5: note: 'swprintf' declared here
int swprintf(wchar_t * __restrict, size_t n, const wchar_t * __restrict,
^
json_ui.cc:60:21: error: cannot initialize a parameter of type 'wchar_t *' with an lvalue of type 'char [7]'
sprintf(buf, "\\u%04x", *next);
^~~
/usr/include/wchar.h:133:34: note: passing argument to parameter here
int swprintf(wchar_t * __restrict, size_t n, const wchar_t * __restrict,
^
json_ui.cc:74:9: error: use of undeclared identifier 'sprintf'
sprintf(buffer, R"("#%02x%02x%02x")", color.r, color.g, color.b);
^
3 errors generated.
---
regex_impl.cc:1039:9: error: use of undeclared identifier 'sprintf'; did you mean 'swprintf'?
sprintf(buf, " %03d ", count++);
^~~~~~~
swprintf
/usr/include/wchar.h:133:5: note: 'swprintf' declared here
int swprintf(wchar_t * __restrict, size_t n, const wchar_t * __restrict,
^
regex_impl.cc:1039:17: error: cannot initialize a parameter of type 'wchar_t *' with an lvalue of type 'char [20]'
sprintf(buf, " %03d ", count++);
^~~
/usr/include/wchar.h:133:34: note: passing argument to parameter here
int swprintf(wchar_t * __restrict, size_t n, const wchar_t * __restrict,
^
regex_impl.cc:1197:17: error: use of undeclared identifier 'puts'
{ if (dump) puts(dump_regex(*this).c_str()); }
^
regex_impl.cc:1208:18: note: in instantiation of member function 'Kakoune::(anonymous namespace)::TestVM<Kakoune::RegexMode::Forward>::TestVM' requested here
TestVM<> vm{R"(a*b)"};
^
regex_impl.cc:1197:17: error: use of undeclared identifier 'puts'
{ if (dump) puts(dump_regex(*this).c_str()); }
^
regex_impl.cc:1283:56: note: in instantiation of member function 'Kakoune::(anonymous namespace)::TestVM<5>::TestVM' requested here
TestVM<RegexMode::Forward | RegexMode::Search> vm{R"(f.*a(.*o))"};
^
regex_impl.cc:1197:17: error: use of undeclared identifier 'puts'
{ if (dump) puts(dump_regex(*this).c_str()); }
^
regex_impl.cc:1423:57: note: in instantiation of member function 'Kakoune::(anonymous namespace)::TestVM<6>::TestVM' requested here
TestVM<RegexMode::Backward | RegexMode::Search> vm{R"(fo{1,})"};
^
5 errors generated.
---
remote.cc:829:9: error: use of undeclared identifier 'rename'; did you mean 'devname'?
if (rename(old_socket_file.c_str(), new_socket_file.c_str()) != 0)
^~~~~~
devname
/usr/include/stdlib.h:277:7: note: 'devname' declared here
char *devname(__dev_t, __mode_t);
^
remote.cc:829:16: error: cannot initialize a parameter of type '__dev_t' (aka 'unsigned long') with an rvalue of type 'const char *'
if (rename(old_socket_file.c_str(), new_socket_file.c_str()) != 0)
^~~~~~~~~~~~~~~~~~~~~~~
/usr/include/stdlib.h:277:22: note: passing argument to parameter here
char *devname(__dev_t, __mode_t);
^
2 errors generated.
---
string_utils.cc:126:20: error: use of undeclared identifier 'sprintf'; did you mean 'swprintf'?
res.m_length = sprintf(res.m_data, "%i", val);
^~~~~~~
swprintf
/usr/include/wchar.h:133:5: note: 'swprintf' declared here
int swprintf(wchar_t * __restrict, size_t n, const wchar_t * __restrict,
^
string_utils.cc:126:28: error: cannot initialize a parameter of type 'wchar_t *' with an lvalue of type 'char [15]'
res.m_length = sprintf(res.m_data, "%i", val);
^~~~~~~~~~
/usr/include/wchar.h:133:34: note: passing argument to parameter here
int swprintf(wchar_t * __restrict, size_t n, const wchar_t * __restrict,
^
string_utils.cc:133:20: error: use of undeclared identifier 'sprintf'; did you mean 'swprintf'?
res.m_length = sprintf(res.m_data, "%u", val);
^~~~~~~
swprintf
[...]
2019-07-01 16:32:14 +02:00
|
|
|
#include <cstdio>
|
2016-03-06 02:37:20 +01:00
|
|
|
#include <utility>
|
|
|
|
|
2016-04-21 01:06:43 +02:00
|
|
|
#include <unistd.h>
|
|
|
|
|
2016-03-06 02:37:20 +01:00
|
|
|
namespace Kakoune
|
|
|
|
{
|
|
|
|
|
2018-08-18 08:23:18 +02:00
|
|
|
struct invalid_rpc_request : runtime_error {
|
|
|
|
invalid_rpc_request(String message)
|
|
|
|
: runtime_error(format("invalid json rpc request ({})", message)) {}
|
|
|
|
};
|
|
|
|
|
2016-03-06 02:37:20 +01:00
|
|
|
template<typename T>
|
|
|
|
String to_json(ArrayView<const T> array)
|
|
|
|
{
|
2018-04-29 15:05:46 +02:00
|
|
|
return "[" + join(array | transform([](auto&& elem) { return to_json(elem); }), ", ") + "]";
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T, MemoryDomain D>
|
|
|
|
String to_json(const Vector<T, D>& vec) { return to_json(ArrayView<const T>{vec}); }
|
|
|
|
|
2018-04-29 12:38:47 +02:00
|
|
|
template<typename K, typename V, MemoryDomain D>
|
|
|
|
String to_json(const HashMap<K, V, D>& map)
|
|
|
|
{
|
|
|
|
return "{" + join(map | transform([](auto&& i) { return format("{}: {}", to_json(i.key), to_json(i.value)); }),
|
|
|
|
',', false) + "}";
|
|
|
|
}
|
|
|
|
|
2016-03-06 02:37:20 +01:00
|
|
|
String to_json(int i) { return to_string(i); }
|
2016-03-08 00:14:28 +01:00
|
|
|
String to_json(bool b) { return b ? "true" : "false"; }
|
2016-03-07 23:38:37 +01:00
|
|
|
String to_json(StringView str)
|
|
|
|
{
|
|
|
|
String res;
|
|
|
|
res.reserve(str.length() + 4);
|
|
|
|
res += '"';
|
|
|
|
for (auto it = str.begin(), end = str.end(); it != end; )
|
|
|
|
{
|
|
|
|
auto next = std::find_if(it, end, [](char c) {
|
|
|
|
return c == '\\' or c == '"' or (c >= 0 and c <= 0x1F);
|
|
|
|
});
|
|
|
|
|
|
|
|
res += StringView{it, next};
|
|
|
|
if (next == end)
|
|
|
|
break;
|
|
|
|
|
|
|
|
char buf[7] = {'\\', *next, 0};
|
|
|
|
if (*next >= 0 and *next <= 0x1F)
|
|
|
|
sprintf(buf, "\\u%04x", *next);
|
|
|
|
|
|
|
|
res += buf;
|
|
|
|
it = next+1;
|
|
|
|
}
|
|
|
|
res += '"';
|
|
|
|
return res;
|
|
|
|
}
|
2016-03-06 02:37:20 +01:00
|
|
|
|
|
|
|
String to_json(Color color)
|
|
|
|
{
|
|
|
|
if (color.color == Kakoune::Color::RGB)
|
|
|
|
{
|
|
|
|
char buffer[10];
|
|
|
|
sprintf(buffer, R"("#%02x%02x%02x")", color.r, color.g, color.b);
|
|
|
|
return buffer;
|
|
|
|
}
|
2017-09-12 05:31:57 +02:00
|
|
|
return to_json(to_string(color));
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
String to_json(Attribute attributes)
|
|
|
|
{
|
2017-01-05 00:19:18 +01:00
|
|
|
struct Attr { Attribute attr; StringView name; }
|
2018-04-05 00:52:33 +02:00
|
|
|
attrs[] {
|
2016-03-06 02:37:20 +01:00
|
|
|
{ Attribute::Underline, "underline" },
|
|
|
|
{ Attribute::Reverse, "reverse" },
|
|
|
|
{ Attribute::Blink, "blink" },
|
|
|
|
{ Attribute::Bold, "bold" },
|
|
|
|
{ Attribute::Dim, "dim" },
|
|
|
|
{ Attribute::Italic, "italic" },
|
2018-09-23 15:17:12 +02:00
|
|
|
{ Attribute::FinalFg, "final_fg" },
|
|
|
|
{ Attribute::FinalBg, "final_bg" },
|
|
|
|
{ Attribute::FinalAttr, "final_attr" },
|
2016-03-06 02:37:20 +01:00
|
|
|
};
|
|
|
|
|
2017-01-05 00:19:18 +01:00
|
|
|
return "[" + join(attrs |
|
|
|
|
filter([=](const Attr& a) { return attributes & a.attr; }) |
|
|
|
|
transform([](const Attr& a) { return to_json(a.name); }),
|
|
|
|
',', false) + "]";
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
String to_json(Face face)
|
|
|
|
{
|
|
|
|
return format(R"(\{ "fg": {}, "bg": {}, "attributes": {} })",
|
|
|
|
to_json(face.fg), to_json(face.bg), to_json(face.attributes));
|
|
|
|
}
|
|
|
|
|
|
|
|
String to_json(const DisplayAtom& atom)
|
|
|
|
{
|
|
|
|
return format(R"(\{ "face": {}, "contents": {} })", to_json(atom.face), to_json(atom.content()));
|
|
|
|
}
|
|
|
|
|
|
|
|
String to_json(const DisplayLine& line)
|
|
|
|
{
|
|
|
|
return to_json(line.atoms());
|
|
|
|
}
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
String to_json(DisplayCoord coord)
|
2016-03-06 02:37:20 +01:00
|
|
|
{
|
|
|
|
return format(R"(\{ "line": {}, "column": {} })", coord.line, coord.column);
|
|
|
|
}
|
|
|
|
|
|
|
|
String to_json(MenuStyle style)
|
|
|
|
{
|
|
|
|
switch (style)
|
|
|
|
{
|
|
|
|
case MenuStyle::Prompt: return R"("prompt")";
|
2018-06-03 04:06:29 +02:00
|
|
|
case MenuStyle::Search: return R"("search")";
|
2016-03-06 02:37:20 +01:00
|
|
|
case MenuStyle::Inline: return R"("inline")";
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
String to_json(InfoStyle style)
|
|
|
|
{
|
|
|
|
switch (style)
|
|
|
|
{
|
|
|
|
case InfoStyle::Prompt: return R"("prompt")";
|
|
|
|
case InfoStyle::Inline: return R"("inline")";
|
|
|
|
case InfoStyle::InlineAbove: return R"("inlineAbove")";
|
|
|
|
case InfoStyle::InlineBelow: return R"("inlineBelow")";
|
|
|
|
case InfoStyle::MenuDoc: return R"("menuDoc")";
|
2017-01-04 12:24:07 +01:00
|
|
|
case InfoStyle::Modal: return R"("modal")";
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2017-04-12 11:39:17 +02:00
|
|
|
String to_json(CursorMode mode)
|
|
|
|
{
|
|
|
|
switch (mode)
|
|
|
|
{
|
|
|
|
case CursorMode::Prompt: return R"("prompt")";
|
|
|
|
case CursorMode::Buffer: return R"("buffer")";
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2016-03-06 02:37:20 +01:00
|
|
|
String concat()
|
|
|
|
{
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename First, typename... Args>
|
|
|
|
String concat(First&& first, Args&&... args)
|
|
|
|
{
|
|
|
|
if (sizeof...(Args) != 0)
|
|
|
|
return to_json(first) + ", " + concat(args...);
|
|
|
|
return to_json(first);
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename... Args>
|
|
|
|
void rpc_call(StringView method, Args&&... args)
|
|
|
|
{
|
|
|
|
auto q = format(R"(\{ "jsonrpc": "2.0", "method": "{}", "params": [{}] }{})",
|
|
|
|
method, concat(std::forward<Args>(args)...), "\n");
|
|
|
|
|
2016-11-29 00:53:50 +01:00
|
|
|
write(1, q);
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
JsonUI::JsonUI()
|
2016-11-30 14:59:08 +01:00
|
|
|
: m_stdin_watcher{0, FdEvents::Read,
|
|
|
|
[this](FDWatcher&, FdEvents, EventMode mode) {
|
2016-03-06 02:37:20 +01:00
|
|
|
parse_requests(mode);
|
|
|
|
}}, m_dimensions{24, 80}
|
|
|
|
{
|
|
|
|
set_signal_handler(SIGINT, SIG_DFL);
|
|
|
|
}
|
|
|
|
|
|
|
|
void JsonUI::draw(const DisplayBuffer& display_buffer,
|
2016-04-11 14:44:10 +02:00
|
|
|
const Face& default_face, const Face& padding_face)
|
2016-03-06 02:37:20 +01:00
|
|
|
{
|
2016-04-11 14:44:10 +02:00
|
|
|
rpc_call("draw", display_buffer.lines(), default_face, padding_face);
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void JsonUI::draw_status(const DisplayLine& status_line,
|
|
|
|
const DisplayLine& mode_line,
|
|
|
|
const Face& default_face)
|
|
|
|
{
|
|
|
|
rpc_call("draw_status", status_line, mode_line, default_face);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void JsonUI::menu_show(ConstArrayView<DisplayLine> items,
|
2016-09-22 21:36:26 +02:00
|
|
|
DisplayCoord anchor, Face fg, Face bg,
|
2016-03-06 02:37:20 +01:00
|
|
|
MenuStyle style)
|
|
|
|
{
|
|
|
|
rpc_call("menu_show", items, anchor, fg, bg, style);
|
|
|
|
}
|
|
|
|
|
|
|
|
void JsonUI::menu_select(int selected)
|
|
|
|
{
|
2016-08-27 17:57:48 +02:00
|
|
|
rpc_call("menu_select", selected);
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void JsonUI::menu_hide()
|
|
|
|
{
|
|
|
|
rpc_call("menu_hide");
|
|
|
|
}
|
|
|
|
|
|
|
|
void JsonUI::info_show(StringView title, StringView content,
|
2016-09-22 21:36:26 +02:00
|
|
|
DisplayCoord anchor, Face face,
|
2016-03-06 02:37:20 +01:00
|
|
|
InfoStyle style)
|
|
|
|
{
|
|
|
|
rpc_call("info_show", title, content, anchor, face, style);
|
|
|
|
}
|
|
|
|
|
|
|
|
void JsonUI::info_hide()
|
|
|
|
{
|
|
|
|
rpc_call("info_hide");
|
|
|
|
}
|
|
|
|
|
2017-04-12 11:39:17 +02:00
|
|
|
void JsonUI::set_cursor(CursorMode mode, DisplayCoord coord)
|
|
|
|
{
|
|
|
|
rpc_call("set_cursor", mode, coord);
|
|
|
|
}
|
|
|
|
|
2016-03-07 14:54:20 +01:00
|
|
|
void JsonUI::refresh(bool force)
|
2016-03-06 02:37:20 +01:00
|
|
|
{
|
2016-03-07 14:54:20 +01:00
|
|
|
rpc_call("refresh", force);
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void JsonUI::set_ui_options(const Options& options)
|
|
|
|
{
|
2018-04-29 12:38:47 +02:00
|
|
|
rpc_call("set_ui_options", options);
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
DisplayCoord JsonUI::dimensions()
|
2016-03-06 02:37:20 +01:00
|
|
|
{
|
|
|
|
return m_dimensions;
|
|
|
|
}
|
|
|
|
|
2016-11-29 22:35:53 +01:00
|
|
|
void JsonUI::set_on_key(OnKeyCallback callback)
|
|
|
|
{
|
|
|
|
m_on_key = std::move(callback);
|
|
|
|
}
|
|
|
|
|
2016-03-06 02:37:20 +01:00
|
|
|
using JsonArray = Vector<Value>;
|
2017-03-07 02:12:37 +01:00
|
|
|
using JsonObject = HashMap<String, Value>;
|
2016-03-06 02:37:20 +01:00
|
|
|
|
|
|
|
static bool is_digit(char c) { return c >= '0' and c <= '9'; }
|
|
|
|
|
2019-01-24 10:41:19 +01:00
|
|
|
struct JsonResult { Value value; const char* new_pos; };
|
2016-03-06 02:37:20 +01:00
|
|
|
|
2019-01-24 10:41:19 +01:00
|
|
|
JsonResult parse_json(const char* pos, const char* end)
|
|
|
|
{
|
2016-03-06 02:37:20 +01:00
|
|
|
if (not skip_while(pos, end, is_blank))
|
|
|
|
return {};
|
|
|
|
|
|
|
|
if (is_digit(*pos))
|
|
|
|
{
|
|
|
|
auto digit_end = pos;
|
|
|
|
skip_while(digit_end, end, is_digit);
|
2019-01-24 10:41:19 +01:00
|
|
|
return { Value{str_to_int({pos, digit_end})}, digit_end };
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
2016-03-08 01:28:53 +01:00
|
|
|
if (end - pos > 4 and StringView{pos, pos+4} == "true")
|
2019-01-24 10:41:19 +01:00
|
|
|
return { Value{true}, pos+4 };
|
2016-03-08 01:28:53 +01:00
|
|
|
if (end - pos > 5 and StringView{pos, pos+5} == "false")
|
2019-01-24 10:41:19 +01:00
|
|
|
return { Value{false}, pos+5 };
|
2016-03-06 02:37:20 +01:00
|
|
|
if (*pos == '"')
|
|
|
|
{
|
|
|
|
String value;
|
|
|
|
bool escaped = false;
|
|
|
|
++pos;
|
|
|
|
for (auto string_end = pos; string_end != end; ++string_end)
|
|
|
|
{
|
|
|
|
if (escaped)
|
|
|
|
{
|
|
|
|
escaped = false;
|
|
|
|
value += StringView{pos, string_end};
|
|
|
|
value.back() = *string_end;
|
|
|
|
pos = string_end+1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (*string_end == '\\')
|
|
|
|
escaped = true;
|
|
|
|
if (*string_end == '"')
|
|
|
|
{
|
|
|
|
value += StringView{pos, string_end};
|
2019-01-24 10:41:19 +01:00
|
|
|
return {std::move(value), string_end+1};
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
if (*pos == '[')
|
|
|
|
{
|
|
|
|
JsonArray array;
|
2018-02-19 21:41:44 +01:00
|
|
|
if (++pos == end)
|
|
|
|
throw runtime_error("unable to parse array");
|
|
|
|
if (*pos == ']')
|
2019-01-24 10:41:19 +01:00
|
|
|
return {std::move(array), pos+1};
|
2016-06-11 14:41:46 +02:00
|
|
|
|
2016-03-06 02:37:20 +01:00
|
|
|
while (true)
|
|
|
|
{
|
2019-01-24 10:41:19 +01:00
|
|
|
auto [element, new_pos] = parse_json(pos, end);
|
2016-03-06 02:37:20 +01:00
|
|
|
if (not element)
|
|
|
|
return {};
|
2019-01-24 10:41:19 +01:00
|
|
|
pos = new_pos;
|
2016-03-06 02:37:20 +01:00
|
|
|
array.push_back(std::move(element));
|
|
|
|
if (not skip_while(pos, end, is_blank))
|
|
|
|
return {};
|
|
|
|
|
|
|
|
if (*pos == ',')
|
|
|
|
++pos;
|
2016-03-06 15:23:56 +01:00
|
|
|
else if (*pos == ']')
|
2019-01-24 10:41:19 +01:00
|
|
|
return {std::move(array), pos+1};
|
2016-03-06 02:37:20 +01:00
|
|
|
else
|
2016-03-06 15:23:56 +01:00
|
|
|
throw runtime_error("unable to parse array, expected ',' or ']'");
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (*pos == '{')
|
|
|
|
{
|
2018-02-19 21:41:44 +01:00
|
|
|
if (++pos == end)
|
|
|
|
throw runtime_error("unable to parse object");
|
2016-03-06 02:37:20 +01:00
|
|
|
JsonObject object;
|
2018-02-19 21:41:44 +01:00
|
|
|
if (*pos == '}')
|
2019-01-24 10:41:19 +01:00
|
|
|
return {std::move(object), pos+1};
|
2016-06-11 14:41:46 +02:00
|
|
|
|
2016-03-06 02:37:20 +01:00
|
|
|
while (true)
|
|
|
|
{
|
2019-01-24 10:41:19 +01:00
|
|
|
auto [name_value, name_end] = parse_json(pos, end);
|
2016-03-06 02:37:20 +01:00
|
|
|
if (not name_value)
|
|
|
|
return {};
|
2019-01-24 10:41:19 +01:00
|
|
|
pos = name_end;
|
2016-03-06 02:37:20 +01:00
|
|
|
String& name = name_value.as<String>();
|
|
|
|
if (not skip_while(pos, end, is_blank))
|
|
|
|
return {};
|
|
|
|
if (*pos++ != ':')
|
|
|
|
throw runtime_error("expected :");
|
|
|
|
|
2019-01-24 10:41:19 +01:00
|
|
|
auto [element, element_end] = parse_json(pos, end);
|
2016-03-06 02:37:20 +01:00
|
|
|
if (not element)
|
|
|
|
return {};
|
2019-01-24 10:41:19 +01:00
|
|
|
pos = element_end;
|
2017-03-07 02:12:37 +01:00
|
|
|
object.insert({ std::move(name), std::move(element) });
|
2016-03-06 02:37:20 +01:00
|
|
|
if (not skip_while(pos, end, is_blank))
|
|
|
|
return {};
|
|
|
|
|
|
|
|
if (*pos == ',')
|
|
|
|
++pos;
|
2016-03-06 15:23:56 +01:00
|
|
|
else if (*pos == '}')
|
2019-01-24 10:41:19 +01:00
|
|
|
return {std::move(object), pos+1};
|
2016-03-06 02:37:20 +01:00
|
|
|
else
|
2016-03-06 15:23:56 +01:00
|
|
|
throw runtime_error("unable to parse object, expected ',' or '}'");
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
|
|
|
}
|
2018-04-06 16:56:53 +02:00
|
|
|
throw runtime_error("unable to parse json");
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
|
|
|
|
2019-01-24 10:41:19 +01:00
|
|
|
auto parse_json(StringView json) { return parse_json(json.begin(), json.end()); }
|
2016-06-11 14:41:46 +02:00
|
|
|
|
2016-03-06 02:37:20 +01:00
|
|
|
void JsonUI::eval_json(const Value& json)
|
|
|
|
{
|
2016-07-04 20:31:09 +02:00
|
|
|
if (not json.is_a<JsonObject>())
|
2018-08-18 08:23:18 +02:00
|
|
|
throw invalid_rpc_request("request is not an object");
|
2016-07-04 20:31:09 +02:00
|
|
|
|
2016-03-06 02:37:20 +01:00
|
|
|
const JsonObject& object = json.as<JsonObject>();
|
2017-03-07 01:30:54 +01:00
|
|
|
auto json_it = object.find("jsonrpc"_sv);
|
2018-08-18 08:23:18 +02:00
|
|
|
if (json_it == object.end() or
|
|
|
|
not json_it->value.is_a<String>() or
|
|
|
|
json_it->value.as<String>() != "2.0")
|
|
|
|
throw invalid_rpc_request("only protocol '2.0' is supported");
|
|
|
|
else if (not json_it->value.is_a<String>())
|
|
|
|
throw invalid_rpc_request("'jsonrpc' is not a string");
|
2016-03-06 02:37:20 +01:00
|
|
|
|
2017-03-07 01:30:54 +01:00
|
|
|
auto method_it = object.find("method"_sv);
|
2016-03-06 02:37:20 +01:00
|
|
|
if (method_it == object.end())
|
2018-08-18 08:23:18 +02:00
|
|
|
throw invalid_rpc_request("method missing");
|
|
|
|
else if (not method_it->value.is_a<String>())
|
|
|
|
throw invalid_rpc_request("'method' is not a string");
|
2016-03-06 02:37:20 +01:00
|
|
|
StringView method = method_it->value.as<String>();
|
|
|
|
|
2017-03-07 01:30:54 +01:00
|
|
|
auto params_it = object.find("params"_sv);
|
2016-03-06 02:37:20 +01:00
|
|
|
if (params_it == object.end())
|
2018-08-18 08:23:18 +02:00
|
|
|
throw invalid_rpc_request("params missing");
|
|
|
|
else if (not params_it->value.is_a<JsonArray>())
|
|
|
|
throw invalid_rpc_request("'params' is not an array");
|
2016-03-06 02:37:20 +01:00
|
|
|
const JsonArray& params = params_it->value.as<JsonArray>();
|
|
|
|
|
|
|
|
if (method == "keys")
|
|
|
|
{
|
|
|
|
for (auto& key_val : params)
|
|
|
|
{
|
2018-08-18 08:23:18 +02:00
|
|
|
if (not key_val.is_a<String>())
|
|
|
|
throw invalid_rpc_request("'keys' is not an array of strings");
|
|
|
|
|
2016-03-06 02:37:20 +01:00
|
|
|
for (auto& key : parse_keys(key_val.as<String>()))
|
2016-11-29 22:35:53 +01:00
|
|
|
m_on_key(key);
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
|
|
|
}
|
2018-05-03 14:17:24 +02:00
|
|
|
else if (method == "mouse")
|
|
|
|
{
|
|
|
|
if (params.size() != 3)
|
2018-08-18 08:23:18 +02:00
|
|
|
throw invalid_rpc_request("mouse type/coordinates not specified");
|
|
|
|
|
|
|
|
if (not params[0].is_a<String>())
|
|
|
|
throw invalid_rpc_request("mouse type is not a string");
|
|
|
|
else if (not params[1].is_a<int>() or
|
|
|
|
not params[2].is_a<int>())
|
|
|
|
throw invalid_rpc_request("mouse coordinates are not integers");
|
2018-05-03 14:17:24 +02:00
|
|
|
|
|
|
|
const StringView type = params[0].as<String>();
|
|
|
|
const Codepoint coord = encode_coord({params[1].as<int>(), params[2].as<int>()});
|
|
|
|
if (type == "move")
|
|
|
|
m_on_key({Key::Modifiers::MousePos, coord});
|
2018-12-18 16:22:50 +01:00
|
|
|
else if (type == "press_left")
|
|
|
|
m_on_key({Key::Modifiers::MousePressLeft, coord});
|
|
|
|
else if (type == "press_right")
|
|
|
|
m_on_key({Key::Modifiers::MousePressRight, coord});
|
|
|
|
else if (type == "release_left")
|
|
|
|
m_on_key({Key::Modifiers::MouseReleaseLeft, coord});
|
|
|
|
else if (type == "release_right")
|
|
|
|
m_on_key({Key::Modifiers::MouseReleaseRight, coord});
|
2018-05-03 14:17:24 +02:00
|
|
|
else
|
2018-08-18 08:23:18 +02:00
|
|
|
throw invalid_rpc_request(format("invalid mouse event type: {}", type));
|
2018-05-03 14:17:24 +02:00
|
|
|
}
|
2019-08-19 14:16:39 +02:00
|
|
|
else if (method == "scroll")
|
|
|
|
{
|
|
|
|
if (params.size() != 1)
|
|
|
|
throw invalid_rpc_request("scroll needs an amount");
|
|
|
|
else if (not params[0].is_a<int>())
|
|
|
|
throw invalid_rpc_request("scroll amount is not an integer");
|
|
|
|
m_on_key({Key::Modifiers::Scroll, (Codepoint)params[0].as<int>()});
|
|
|
|
|
|
|
|
}
|
2018-05-03 14:22:12 +02:00
|
|
|
else if (method == "menu_select")
|
|
|
|
{
|
|
|
|
if (params.size() != 1)
|
2018-08-18 08:23:18 +02:00
|
|
|
throw invalid_rpc_request("menu_select needs the item index");
|
|
|
|
else if (not params[0].is_a<int>())
|
|
|
|
throw invalid_rpc_request("menu index is not an integer");
|
|
|
|
|
2018-05-03 14:22:12 +02:00
|
|
|
m_on_key({Key::Modifiers::MenuSelect, (Codepoint)params[0].as<int>()});
|
|
|
|
}
|
2016-03-06 15:23:56 +01:00
|
|
|
else if (method == "resize")
|
|
|
|
{
|
|
|
|
if (params.size() != 2)
|
|
|
|
throw runtime_error("resize expects 2 parameters");
|
2018-08-18 08:23:18 +02:00
|
|
|
else if (not params[0].is_a<int>() or
|
|
|
|
not params[1].is_a<int>())
|
|
|
|
throw invalid_rpc_request("width and height are not integers");
|
2016-03-06 15:23:56 +01:00
|
|
|
|
2016-09-22 21:36:26 +02:00
|
|
|
DisplayCoord dim{params[0].as<int>(), params[1].as<int>()};
|
2016-03-06 15:23:56 +01:00
|
|
|
m_dimensions = dim;
|
2016-11-29 22:35:53 +01:00
|
|
|
m_on_key(resize(dim));
|
2016-03-06 15:23:56 +01:00
|
|
|
}
|
2016-03-06 02:37:20 +01:00
|
|
|
else
|
2018-08-18 08:23:18 +02:00
|
|
|
throw invalid_rpc_request(format("unknown method: {}", method));
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void JsonUI::parse_requests(EventMode mode)
|
|
|
|
{
|
|
|
|
constexpr size_t bufsize = 1024;
|
|
|
|
char buf[bufsize];
|
2016-08-30 23:56:47 +02:00
|
|
|
while (fd_readable(0))
|
2016-03-06 02:37:20 +01:00
|
|
|
{
|
2016-04-21 01:06:43 +02:00
|
|
|
ssize_t size = ::read(0, buf, bufsize);
|
2016-03-06 02:37:20 +01:00
|
|
|
if (size == -1 or size == 0)
|
2018-04-29 14:27:28 +02:00
|
|
|
{
|
|
|
|
m_stdin_watcher.close_fd();
|
2016-03-06 02:37:20 +01:00
|
|
|
break;
|
2018-04-29 14:27:28 +02:00
|
|
|
}
|
2016-03-06 02:37:20 +01:00
|
|
|
|
2016-03-06 15:23:56 +01:00
|
|
|
m_requests += StringView{buf, buf + size};
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
|
|
|
|
2016-11-29 22:35:53 +01:00
|
|
|
if (not m_on_key)
|
|
|
|
return;
|
|
|
|
|
2016-08-25 00:38:50 +02:00
|
|
|
while (not m_requests.empty())
|
2016-03-06 02:37:20 +01:00
|
|
|
{
|
2016-06-29 22:08:16 +02:00
|
|
|
const char* pos = nullptr;
|
2016-06-11 14:41:46 +02:00
|
|
|
try
|
|
|
|
{
|
2019-01-24 10:41:19 +01:00
|
|
|
auto [json, new_pos] = parse_json(m_requests);
|
|
|
|
pos = new_pos;
|
2016-06-29 22:08:16 +02:00
|
|
|
if (json)
|
|
|
|
eval_json(json);
|
2016-06-11 14:41:46 +02:00
|
|
|
}
|
|
|
|
catch (runtime_error& error)
|
|
|
|
{
|
2018-05-16 15:26:05 +02:00
|
|
|
write(2, format("error while handling requests '{}': '{}'\n",
|
2016-11-29 00:53:50 +01:00
|
|
|
m_requests, error.what()));
|
2016-06-29 22:08:16 +02:00
|
|
|
// try to salvage request by dropping its first line
|
2016-07-04 20:31:09 +02:00
|
|
|
pos = std::min(m_requests.end(), find(m_requests, '\n')+1);
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
2016-08-25 00:38:50 +02:00
|
|
|
if (not pos)
|
|
|
|
break; // unterminated request ?
|
|
|
|
|
|
|
|
m_requests = String{pos, m_requests.end()};
|
2016-03-06 02:37:20 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
UnitTest test_json_parser{[]()
|
|
|
|
{
|
2016-06-11 14:41:46 +02:00
|
|
|
{
|
2019-01-24 10:41:19 +01:00
|
|
|
auto value = parse_json(R"({ "jsonrpc": "2.0", "method": "keys", "params": [ "b", "l", "a", "h" ] })").value;
|
2016-06-11 14:41:46 +02:00
|
|
|
kak_assert(value);
|
|
|
|
}
|
|
|
|
|
2016-06-20 16:59:32 +02:00
|
|
|
{
|
2019-01-24 10:41:19 +01:00
|
|
|
auto value = parse_json("[10,20]").value;
|
2016-06-20 16:59:32 +02:00
|
|
|
kak_assert(value and value.is_a<JsonArray>());
|
|
|
|
kak_assert(value.as<JsonArray>().at(1).as<int>() == 20);
|
|
|
|
}
|
|
|
|
|
2016-06-11 14:41:46 +02:00
|
|
|
{
|
2019-01-24 10:41:19 +01:00
|
|
|
auto value = parse_json("{}").value;
|
2016-06-11 14:41:46 +02:00
|
|
|
kak_assert(value and value.is_a<JsonObject>());
|
|
|
|
kak_assert(value.as<JsonObject>().empty());
|
|
|
|
}
|
2016-03-06 02:37:20 +01:00
|
|
|
}};
|
|
|
|
|
|
|
|
}
|