Skip to content

Instantly share code, notes, and snippets.

@tadeokondrak
Last active February 26, 2020 05:35
Show Gist options
  • Select an option

  • Save tadeokondrak/da159465c43587d9afb1936a9ecb6b3e to your computer and use it in GitHub Desktop.

Select an option

Save tadeokondrak/da159465c43587d9afb1936a9ecb6b3e to your computer and use it in GitHub Desktop.
kakoune_mujs.patch, applies on 93a889bd44ceb5d3d4656d251ddbbd4a88b3fe1b. allows returning one value converted to string, kak.command('args'...) works, and a few other builtin accessors
diff --git a/src/Makefile b/src/Makefile
index 123ef1d8..5550e732 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -85,8 +85,8 @@ else
$(error "pkg-config not found in PATH")
endif
- LIBS += $(shell $(PKG_CONFIG) $(PKG_CONFIG_FLAGS) --libs ncursesw)
- NCURSES_CFLAGS += $(shell $(PKG_CONFIG) $(PKG_CONFIG_FLAGS) --cflags ncursesw)
+ LIBS += $(shell $(PKG_CONFIG) $(PKG_CONFIG_FLAGS) --libs ncursesw mujs)
+ NCURSES_CFLAGS += $(shell $(PKG_CONFIG) $(PKG_CONFIG_FLAGS) --cflags ncursesw mujs)
LDFLAGS += -rdynamic
endif
diff --git a/src/command_manager.cc b/src/command_manager.cc
index 82985b5d..2669e06c 100644
--- a/src/command_manager.cc
+++ b/src/command_manager.cc
@@ -6,6 +6,7 @@
#include "context.hh"
#include "flags.hh"
#include "file.hh"
+#include "javascript_manager.hh"
#include "optional.hh"
#include "option_types.hh"
#include "ranges.hh"
@@ -206,6 +207,8 @@ Token::Type token_type(StringView type_name, bool throw_on_invalid)
return Token::Type::RawQuoted;
else if (type_name == "sh")
return Token::Type::ShellExpand;
+ else if (type_name == "js")
+ return Token::Type::JavascriptExpand;
else if (type_name == "reg")
return Token::Type::RegisterExpand;
else if (type_name == "opt")
@@ -353,6 +356,8 @@ expand_token(const Token& token, const Context& context, const ShellContext& she
str.resize(str.length() - trailing_eol_count, 0);
return {str};
}
+ case Token::Type::JavascriptExpand:
+ return {JavascriptManager::instance().eval(content, context, shell_context)};
case Token::Type::RegisterExpand:
return expand_register(content, context, IsSingle{});
case Token::Type::OptionExpand:
diff --git a/src/command_manager.hh b/src/command_manager.hh
index affec4e3..18c9e586 100644
--- a/src/command_manager.hh
+++ b/src/command_manager.hh
@@ -47,6 +47,7 @@ struct Token
RawQuoted,
RawEval,
ShellExpand,
+ JavascriptExpand,
RegisterExpand,
OptionExpand,
ValExpand,
diff --git a/src/javascript_manager.cc b/src/javascript_manager.cc
new file mode 100644
index 00000000..bc4333a7
--- /dev/null
+++ b/src/javascript_manager.cc
@@ -0,0 +1,254 @@
+#include "javascript_manager.hh"
+
+#include "buffer_utils.hh"
+#include "command_manager.hh"
+#include "file.hh"
+#include "register_manager.hh"
+
+#include <cstring>
+
+namespace Kakoune
+{
+
+namespace
+{
+
+struct JavascriptContext
+{
+ const Context& context;
+ const ShellContext& shell_context;
+};
+
+}
+
+JavascriptManager::JavascriptManager()
+ : J(js_newstate(nullptr, nullptr, JS_STRICT))
+{
+ js_pushglobal(J);
+
+ js_newcfunction(J, kak, "kak", 0);
+ js_setregistry(J, "kak");
+
+ js_getproperty(J, -1, "Object");
+ js_newuserdatax(J, "kak", nullptr, kak_hasproperty, nullptr, nullptr, nullptr);
+ js_defproperty(J, -2, "kak", JS_READONLY | JS_DONTENUM | JS_DONTCONF);
+
+ js_newcfunction(J, sh, "sh", 1);
+ js_defproperty(J, -2, "sh", JS_READONLY | JS_DONTENUM | JS_DONTCONF);
+
+ js_newcfunction(J, file, "file", 1);
+ js_defproperty(J, -2, "file", JS_READONLY | JS_DONTENUM | JS_DONTCONF);
+
+ js_getproperty(J, -1, "Object");
+ js_newuserdatax(J, "main_reg", nullptr, main_reg_hasproperty, nullptr, nullptr, nullptr);
+ js_defproperty(J, -2, "main_reg", JS_READONLY | JS_DONTENUM | JS_DONTCONF);
+
+ js_getproperty(J, -1, "Object");
+ js_newuserdatax(J, "reg", nullptr, reg_hasproperty, nullptr, nullptr, nullptr);
+ js_defproperty(J, -2, "reg", JS_READONLY | JS_DONTENUM | JS_DONTCONF);
+
+ js_getproperty(J, -1, "Object");
+ js_newuserdatax(J, "opts", nullptr, opts_hasproperty, nullptr, nullptr, nullptr);
+ js_defproperty(J, -2, "opts", JS_READONLY | JS_DONTENUM | JS_DONTCONF);
+
+ js_getproperty(J, -1, "Object");
+ js_newuserdatax(J, "vars", nullptr, vars_hasproperty, nullptr, nullptr, nullptr);
+ js_defproperty(J, -2, "vars", JS_READONLY | JS_DONTENUM | JS_DONTCONF);
+
+ js_newcfunction(J, args, "args", 0);
+ js_pushnull(J);
+ js_defaccessor(J, -3, "args", JS_READONLY | JS_DONTENUM | JS_DONTCONF);
+
+ js_pop(J, 1);
+}
+
+JavascriptManager::~JavascriptManager()
+{
+ js_gc(J, 0);
+ js_freestate(J);
+}
+
+String JavascriptManager::eval(StringView script, const Context& context,
+ const ShellContext& shell_context)
+{
+ JavascriptContext jsctx{context, shell_context};
+ void* last_context = js_getcontext(J);
+ auto pop_context = on_scope_end([&]{ js_setcontext(J, last_context); });
+ js_setcontext(J, &jsctx);
+ if (js_ploadstring(J, "input", script.zstr()) == 0)
+ {
+ if (js_pcall(J, -1) == 0)
+ {
+ auto ret = String(js_trystring(J, -1, ""));
+ js_pop(J, 1);
+ return ret;
+ }
+ }
+ write_to_debug_buffer(format("mujs error: {}", js_trystring(J, -1, "")));
+ js_pop(J, 1);
+ return "";
+}
+
+void JavascriptManager::report(js_State* J, const char* message)
+{
+ write_to_debug_buffer(format("mujs report: {}", message));
+}
+
+void JavascriptManager::kak(js_State* J) noexcept
+{
+ auto& ctx = *reinterpret_cast<JavascriptContext*>(js_getcontext(J));
+ Vector<String> args;
+ int top = js_gettop(J);
+ for (int i = 1; i < top; ++i)
+ {
+ void* save = js_savetry(J);
+ jmp_buf buf;
+ memcpy(buf, &save, sizeof(void*));
+ if (setjmp(buf))
+ {
+ args.~Vector<String>();
+ js_throw(J);
+ }
+ args.push_back(js_tostring(J, i));
+ js_endtry(J);
+ }
+ try
+ {
+ CommandManager::instance().execute_single_command(
+ args, const_cast<Context&>(ctx.context), ctx.shell_context, {});
+ }
+ catch (exception& error)
+ {
+ args.~Vector<String>();
+ js_error(J, "%s", static_cast<const char*>(error.what().zstr()));
+ }
+ js_pushundefined(J);
+}
+
+void JavascriptManager::sh(js_State* J) noexcept
+{
+ auto& ctx = *reinterpret_cast<JavascriptContext*>(js_getcontext(J));
+ int code;
+ String content;
+ std::tie(content, code) = ShellManager::instance().eval(
+ js_trystring(J, -1, ""), ctx.context, js_trystring(J, -2, ""),
+ ShellManager::Flags::WaitForStdout, ctx.shell_context);
+ if (code == 0)
+ {
+ js_pushstring(J, content.c_str());
+ return;
+ }
+ content.~String();
+ js_error(J, "shell command exited with exit code %d", code);
+}
+
+void JavascriptManager::file(js_State* J) noexcept
+{
+ String contents;
+ try
+ {
+ contents = read_file(js_trystring(J, -1, ""));
+ }
+ catch (exception& error)
+ {
+ contents.~String();
+ js_error(J, "%s", static_cast<const char*>(error.what().zstr()));
+ }
+ js_pushstring(J, contents.c_str());
+}
+
+int JavascriptManager::kak_hasproperty(js_State* J, void*, const char* name) noexcept
+{
+ js_newstring(J, name);
+ if (not CommandManager::instance().command_defined(js_tostring(J, -1)))
+ return 0;
+ js_getglobal(J, "Function");
+ js_getproperty(J, -1, "prototype");
+ js_getproperty(J, -1, "bind");
+ js_getregistry(J, "kak");
+ js_dup(J);
+ js_copy(J, -6);
+ js_call(J, 2);
+ return 1;
+}
+
+int JavascriptManager::main_reg_hasproperty(js_State* J, void*, const char* name) noexcept {
+ auto& ctx = *reinterpret_cast<JavascriptContext*>(js_getcontext(J));
+ try
+ {
+ js_newstring(J, ctx.context.main_sel_register_value(name).zstr());
+ return 1;
+ }
+ catch (exception& error)
+ {
+ return 0;
+ }
+}
+
+int JavascriptManager::reg_hasproperty(js_State* J, void*, const char* name) noexcept {
+ auto& ctx = *reinterpret_cast<JavascriptContext*>(js_getcontext(J));
+ try
+ {
+ Register& reg = RegisterManager::instance()[name];
+ js_newarray(J);
+ int i = 0;
+ for (const String& s : reg.get(ctx.context))
+ {
+ js_newstring(J, s.c_str());
+ js_setindex(J, -2, i++);
+ }
+ return 1;
+ }
+ catch (exception& error)
+ {
+ return 0;
+ }
+}
+
+int JavascriptManager::opts_hasproperty(js_State* J, void*, const char* name) noexcept {
+ auto& ctx = *reinterpret_cast<JavascriptContext*>(js_getcontext(J));
+ try
+ {
+ js_newarray(J);
+ int i = 0;
+ for (const String& s : ctx.context.options()[name].get_as_strings())
+ {
+ js_newstring(J, s.c_str());
+ js_setindex(J, -2, i++);
+ }
+ return 1;
+ }
+ catch (exception& error)
+ {
+ js_pop(J, 1);
+ return 0;
+ }
+}
+
+int JavascriptManager::vars_hasproperty(js_State* J, void*, const char* name) noexcept {
+ auto& ctx = *reinterpret_cast<JavascriptContext*>(js_getcontext(J));
+ try
+ {
+ String ret = ShellManager::instance().get_val(name, ctx.context, Quoting::Raw);
+ js_newstring(J, ret.c_str());
+ return 1;
+ }
+ catch (exception& error)
+ {
+ js_pop(J, 1);
+ return 0;
+ }
+}
+
+void JavascriptManager::args(js_State* J) noexcept {
+ auto& ctx = *reinterpret_cast<JavascriptContext*>(js_getcontext(J));
+ js_newarray(J);
+ int i = 0;
+ for (const String& s : ctx.shell_context.params)
+ {
+ js_newstring(J, s.c_str());
+ js_setindex(J, -2, i++);
+ }
+}
+
+}
diff --git a/src/javascript_manager.hh b/src/javascript_manager.hh
new file mode 100644
index 00000000..4f2b6495
--- /dev/null
+++ b/src/javascript_manager.hh
@@ -0,0 +1,39 @@
+#ifndef javascript_manager_hh_INCLUDED
+#define javascript_manager_hh_INCLUDED
+
+#include <mujs.h>
+
+#include "shell_manager.hh"
+#include "context.hh"
+#include "string.hh"
+#include "utils.hh"
+
+namespace Kakoune
+{
+
+class JavascriptManager : public Singleton<JavascriptManager>
+{
+public:
+ JavascriptManager();
+ ~JavascriptManager();
+
+ String eval(StringView script, const Context& context, const ShellContext& shell_context);
+
+private:
+ js_State* J;
+
+ static void report(js_State* J, const char* message);
+ static void kak(js_State* J) noexcept;
+ static void sh(js_State* J) noexcept;
+ static void file(js_State* J) noexcept;
+ static int kak_hasproperty(js_State* J, void*, const char* name) noexcept;
+ static int main_reg_hasproperty(js_State* J, void*, const char* name) noexcept;
+ static int reg_hasproperty(js_State* J, void*, const char* name) noexcept;
+ static int opts_hasproperty(js_State* J, void*, const char* name) noexcept;
+ static int vars_hasproperty(js_State* J, void*, const char* name) noexcept;
+ static void args(js_State* J) noexcept;
+};
+
+}
+
+#endif // javascript_manager_hh_INCLUDED
diff --git a/src/main.cc b/src/main.cc
index 79e0793d..15176947 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -12,6 +12,7 @@
#include "file.hh"
#include "highlighters.hh"
#include "insert_completer.hh"
+#include "javascript_manager.hh"
#include "json_ui.hh"
#include "ncurses_ui.hh"
#include "option_types.hh"
@@ -710,6 +711,7 @@ int run_server(StringView session, StringView server_init,
StringRegistry string_registry;
GlobalScope global_scope;
ShellManager shell_manager{builtin_env_vars};
+ JavascriptManager javascript_manager;
CommandManager command_manager;
RegisterManager register_manager;
HighlighterRegistry highlighter_registry;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment