diff --git a/CMakeLists.txt b/CMakeLists.txt index 577ac12..dfcec19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,8 +117,8 @@ configure_file( set(XEUS_OCTAVE_SRC src/xoctave_interpreter.cpp src/xoctave_interpreter.hpp - src/input.cpp - src/input.hpp + src/io.cpp + src/io.hpp src/toolkits/plotly.cpp src/toolkits/plotly.hpp src/toolkits/notebook.cpp diff --git a/src/input.cpp b/src/io.cpp similarity index 55% rename from src/input.cpp rename to src/io.cpp index 059c0ad..bd8ff83 100644 --- a/src/input.cpp +++ b/src/io.cpp @@ -17,7 +17,7 @@ * along with xeus-octave. If not, see . */ -#include "input.hpp" +#include "io.hpp" #include @@ -55,31 +55,59 @@ void _reset_command_editor(); } // namespace -void input::set_command_editor(octave::command_editor& n) { +void input::override(input& n) { _set_command_editor(n); } -void input::reset_command_editor() { + +void input::restore() { _reset_command_editor(); } +input::input(std::function callback) : m_callback(std::move(callback)) {} + std::string input::do_readline(const std::string& prompt, bool&) { - auto& interpreter = dynamic_cast(xeus::get_interpreter()); + return m_callback(prompt); +} - if (interpreter.m_allow_stdin) { - // Print any output if needed - interpreter.do_print_output(); +void output::override(std::ostream& stream, output& buf) { + // Backup previous buffer + buf.p_oldbuf = stream.rdbuf(); - // Perform the read request - auto input = xeus::blocking_input_request(prompt, false); + stream.rdbuf(&buf); +} - // Print newline - std::cout << std::endl; - interpreter.do_print_output(); +void output::restore(std::ostream& stream, output& buf) { + stream.rdbuf(buf.p_oldbuf); +} - return input; +output::output(std::function callback) + : m_callback(std::move(callback)) { +} + +output::int_type output::overflow(output::int_type c) { + std::lock_guard lock(m_mutex); + // Called for each output character. + if (!traits_type::eq_int_type(c, traits_type::eof())) { + m_output.push_back(traits_type::to_char_type(c)); } + return c; +} - throw std::runtime_error("This frontend does not support input requests"); +std::streamsize output::xsputn(const char* s, std::streamsize count) { + std::lock_guard lock(m_mutex); + // Called for a string of characters. + m_output.append(s, count); + return count; +} + +output::int_type output::sync() { + std::lock_guard lock(m_mutex); + // Called in case of flush. + if (!m_output.empty()) { + m_callback(m_output); + m_output.clear(); + } + return 0; } } // namespace xoctave \ No newline at end of file diff --git a/src/input.hpp b/src/io.hpp similarity index 68% rename from src/input.hpp rename to src/io.hpp index 703ad6d..f729cbf 100644 --- a/src/input.hpp +++ b/src/io.hpp @@ -17,8 +17,12 @@ * along with xeus-octave. If not, see . */ -#ifndef INPUT_H -#define INPUT_H +#ifndef IO_H +#define IO_H + +#include +#include +#include #include "octave/cmd-edit.h" @@ -26,8 +30,10 @@ namespace xoctave { class input : public octave::command_editor { public: - static void set_command_editor(octave::command_editor &n); - static void reset_command_editor(); + input(std::function callback); + + static void override(input &n); + static void restore(); std::string do_readline(const std::string &prompt, bool &) override; void do_set_input_stream(FILE *) override {} @@ -43,6 +49,28 @@ class input : public octave::command_editor { void do_insert_text(const std::string &) override {} void do_newline(void) override {} void do_accept_line(void) override {} + +private: + std::function m_callback; +}; + +class output : public std::streambuf { +public: + output(std::function callback); + + static void override(std::ostream &, output &); + static void restore(std::ostream &, output &); + +protected: + int_type overflow(int_type c) override; + std::streamsize xsputn(const char *s, std::streamsize count) override; + int_type sync() override; + + std::function m_callback; + std::string m_output; + std::mutex m_mutex; + + std::streambuf *p_oldbuf; }; } // namespace xoctave diff --git a/src/xoctave_interpreter.cpp b/src/xoctave_interpreter.cpp index 4a71918..465d20b 100644 --- a/src/xoctave_interpreter.cpp +++ b/src/xoctave_interpreter.cpp @@ -48,7 +48,7 @@ #include #include "config.h" -#include "input.hpp" +#include "io.hpp" #include "toolkits/notebook.hpp" #include "toolkits/plotly.hpp" #include "xeus/xinterpreter.hpp" @@ -60,31 +60,6 @@ using namespace octave; namespace xoctave { -void xoctave_interpreter::do_print_output(bool drawnow) { - bool draw = drawnow && (buf_stderr.str().length() || - buf_stdout.str().length()); - - // Print output if necessary - if (!buf_stderr.str().empty()) { - publish_stream("stderr", buf_stderr.str()); - - // Clear stream - buf_stderr.str(""); - buf_stderr.clear(); - } - - if (!buf_stdout.str().empty()) { - publish_stream("stdout", buf_stdout.str()); - - // Clear stream - buf_stdout.str(""); - buf_stdout.clear(); - } - - if (draw) - octave::feval("drawnow"); -} - void xoctave_interpreter::publish_stream(const std::string& name, const std::string& text) { if (!m_silent) xinterpreter::publish_stream(name, text); @@ -112,6 +87,24 @@ void xoctave_interpreter::publish_execution_error(const std::string& ename, xinterpreter::publish_execution_error(ename, evalue, trace_back); } +std::string xoctave_interpreter::blocking_input_request(const std::string& prompt, bool password) { + if (m_allow_stdin) { + // Register the input handler + std::string value; + register_input_handler([&value](const std::string& v) { value = v; }); + + // Send the input request + input_request(prompt, password); + + // Remove input handler + register_input_handler(nullptr); + + return value; + } + + throw std::runtime_error("This frontend does not support input requests"); +} + nl::json xoctave_interpreter::execute_request_impl(int execution_counter, const std::string& code, bool silent, @@ -160,7 +153,6 @@ nl::json xoctave_interpreter::execute_request_impl(int execution_counter, if (stmt_list) { interpreter.get_evaluator().eval(stmt_list, false); - do_print_output(); } else if (str_parser.at_end_of_input()) { exit_status = EOF; break; @@ -168,14 +160,12 @@ nl::json xoctave_interpreter::execute_request_impl(int execution_counter, } } catch (const interrupt_exception&) { interpreter.recover_from_exception(); - do_print_output(); publish_execution_error("Interrupt exception", "Kernel was interrupted", std::vector()); result["status"] = "error"; } catch (const index_exception& e) { error = e.message(); error += "\n" + e.stack_trace(); interpreter.recover_from_exception(); - do_print_output(false); publish_execution_error("Index exception", error, std::vector()); result["status"] = "error"; } catch (const execution_exception& ee) { @@ -183,12 +173,10 @@ nl::json xoctave_interpreter::execute_request_impl(int execution_counter, error += "\n" + ee.stack_trace(); interpreter.get_error_system().save_exception(ee); interpreter.recover_from_exception(); - do_print_output(false); publish_execution_error("Execution exception", error, std::vector()); result["status"] = "error"; } catch (const std::bad_alloc&) { interpreter.recover_from_exception(); - do_print_output(false); publish_execution_error("Out of memory", "Trying to return to prompt", std::vector()); result["status"] = "error"; } @@ -229,8 +217,10 @@ void xoctave_interpreter::configure_impl() { octave::feval("graphics_toolkit", ovl("plotly")); #endif - // Override the default input system - input::set_command_editor(input_handler); + // Override the default io system + input::override(m_stdin); + output::override(std::cout, m_stdout); + output::override(std::cerr, m_stderr); // Create the xoctave package auto xoctave_package = interpreter.get_cdef_manager().make_package("xoctave"); @@ -240,10 +230,6 @@ void xoctave_interpreter::configure_impl() { // Register the xoctave package interpreter.get_cdef_manager().register_package(xoctave_package); - - // Redirect output to string - std::cout.rdbuf(buf_stdout.rdbuf()); - std::cerr.rdbuf(buf_stderr.rdbuf()); } nl::json xoctave_interpreter::complete_request_impl(const std::string& code, @@ -333,8 +319,11 @@ nl::json xoctave_interpreter::kernel_info_request_impl() { } void xoctave_interpreter::shutdown_request_impl() { - // Recover the old input system before shutting down the interpreter - input::reset_command_editor(); + // Recover the old io system before shutting down the interpreter + input::restore(); + output::restore(std::cout, m_stdout); + output::restore(std::cerr, m_stderr); + interpreter.shutdown(); #ifndef NDEBUG diff --git a/src/xoctave_interpreter.hpp b/src/xoctave_interpreter.hpp index 3c634e0..ad28528 100644 --- a/src/xoctave_interpreter.hpp +++ b/src/xoctave_interpreter.hpp @@ -23,11 +23,12 @@ #include #include +#include #include #include #include -#include "input.hpp" +#include "io.hpp" using nlohmann::json; using xeus::xinterpreter; @@ -35,8 +36,6 @@ using xeus::xinterpreter; namespace xoctave { class xoctave_interpreter : public xinterpreter { - friend input; - private: octave::interpreter interpreter; @@ -64,8 +63,6 @@ class xoctave_interpreter : public xinterpreter { void shutdown_request_impl() override; public: - void do_print_output(bool drawnow = true); - void publish_stream(const std::string& name, const std::string& text); void display_data(json data, json metadata = json::object(), json transient = json::object()); void update_display_data(json data, json metadata = json::object(), json transient = json::object()); @@ -73,13 +70,15 @@ class xoctave_interpreter : public xinterpreter { void publish_execution_error(const std::string& ename, const std::string& evalue, const std::vector& trace_back); + std::string blocking_input_request(const std::string& prompt, bool password); private: std::string get_symbol(const std::string& code, int cursor_pos) const; json get_help_for_symbol(const std::string& symbol); - std::stringstream buf_stdout, buf_stderr; - input input_handler; + output m_stdout{std::bind(&xoctave_interpreter::publish_stream, this, "stdout", std::placeholders::_1)}; + output m_stderr{std::bind(&xoctave_interpreter::publish_stream, this, "stderr", std::placeholders::_1)}; + input m_stdin{std::bind(&xoctave_interpreter::blocking_input_request, this, std::placeholders::_1, false)}; bool m_silent, m_allow_stdin; };