changeset 29503:3bfec185c9e2

experimental command window widget with server loop This changeset provides a new experimental proof-of-concept cross-platform command window. It is intended to demonstrate how communication between the GUI command window and the Octave interpreter can work when the GUI is completely responsible for user input instead of having the interpreter wait for input inside readline. This initial implementation uses a simple text box for input and a separate text edit area for output. This design is not intended to be the final arrangement, but was easy to implement for demonstration purposes. These changes also make it possible to run the command-line version of Octave in a similar client-server mode with a function gathering input in one thread and the Octave intepreter running in another. The new command window is not intended to provide a general-purpose terminal window. So running something like "system ('emacs')" will not bring up an instance of Emacs running in the command window. This also means that it will no longer be possible to use an external output pager such as "less" in the command window. OTOH, it is also no longer necessary to fork and exec a separate process on some Unixy systems when Octave starts solely to give up the controlling terminal so that "less" will function properly. With the new terminal window, it is now possible to start Octave in command line mode and later open the GUI desktop window and then return to the command line when the GUI window is closed. Some things that need to be finished: * Merge the input and output windows so that command input and output are interleaved as they are in a normal command window. * Provide real readline command editing in the GUI command window and at the client-server CLI prompt. We can use the readline callback interface for the GUI and the normal readline interface for the CLI. With this design, the command widget and the CLI front end hold a copy of the command history. They do not need to communicate with the interpreter to navigate the command history. * Create interpreter_event messages for handling user input so that the interpreter can ask the GUI to gather user input for functions like "input". * Consider passing results back to the command widget as octave_value objects and allowing the command widget to format and display them instead of passing formatted text from the interpreter to the command widget. * Provide an output pager feature for the GUI command window? Maybe this feature is not necessary since we have scroll bars in the GUI and we can also have the GUI ask the user whether to display output if it is large. ChangeLog: * options-usage.h (usage_string, octave_print_verbose_usage_and_exit): Include --experimental-terminal-widget in list of options. (EXPERIMENTAL_TERMINAL_WIDGET_OPTION): New macro. (long_opts): Include "experimental-terminal-widget" in the list. * octave.h, octave.cc (cmdline_options::cmdline_options): Handle EXPERIMENTAL_TERMINAL_WIDGET_OPTION. (cmdline_options::m_experimental_terminal_widget): New data member. (application::experimental_terminal_widget, cmdline_options::experimental_terminal_widget interpreter::experimental_terminal_widget): New functions. * main.in.cc (fork_and_exec): New static variable. (main): Don't fork and exec if argv includes --experimental-terminal-widget. * interpreter-qobject.h, interpreter-qobject.cc (interpreter_qobject::pause, interpreter_qobject::stop, interpreter_qobject::resume): New functions. (interpreter_qobject::execute): Don't set prompt strings here if using the new terminal widget. If using the new terminal widget, call interpreter::shutdown and emit the shutdown_finished signal after interpreter::execute returns. (interpreter_qobject::shutdown): Don't do anything if using the new terminal widget. * main-window.h, main-window.cc (main_window::handle_octave_ready): Set interpreter prompt strings here if using the new terminal widget. (main_window::close_gui_signal): New signal. (main_window::closeEvent): If using new terminal widget and shutdown is confirmed, simply emit close_gui_signal. (main_window::construct_octave_qt_link): If using new terminal widget, connect qt_interpreter_events::interpreter_output_signal to the terminal_dock_widget::interpreter_output slot and the qt_interpreter_events::update_prompt_signal to the terminal_dock_widget::update_prompt slot. * octave-qobject.h, octave-qobject.cc (base_qobject::interpreter_pause, base_qobject::interpreter_stop, base_qobject::interpreter_resume): New functions. (base_qobject::base_qobject): If using new terminal widget: Don't connect interpreter_qobject::execution_finished signal to octave_qobject::handle_interpreter_execution_finished slot Don't connect octave_qobject::request_interpreter_shutdown signal to the interpreter_qobject::shutdown slot. Do connect the qt_interpreter_events::start_gui_signal signal to the octave_qobject::start_gui slot and simply reload settings and call qt_application::setQuitOnLastWindowClosed. There is no need to create the main window here. (base_qobject::exec): If using new terminal widget, wait for main thread to finish after qt_application::exec returns. (base_qojbect::experimental_terminal_widget, base_qobject::gui_running): New functions. (base_qojbect::start_gui, base_qojbect::close_gui, base_qojbect::interpreter_pause, base_qojbect::interpreter_stop, base_qojbect::interpreter_resume): New slots. (base_qojbect::handle_interpreter_execution_finished): Do nothing if using new terminal widget. * qt-application.cc (qt_application::start_gui_p): Return dummy value if using new terminal widget. * qt-interpreter-events.h, qt-interpreter-events.cc (qt_interpreter_events::start_gui, qt_interpreter_events::close_gui, qt_interpreter_events::interpreter_output, qt_interpreter_events::display_exception, qt_interpreter_events::update_prompt): New functions. (qt_interpreter_events::start_gui_signal, qt_interpreter_events::close_gui_signal, qt_interpreter_events::update_prompt_signal, qt_interpreter_events::interpreter_output_signal): New signals. * command-widget.h, command-widget.cc: New files. * libgui/src/module.mk: Update. * terminal-dock-widget.h, terminal-dock-widget.cc (terminal_dock_widget::m_experimental_terminal_widget): New data member. (terminal_dock_widget::terminal_dock_widget): Optionally create and use new terminal widget. If using new terminal widget, connect command_widget::interpreter_event signals to terminal_dock_widget::interpreter_event signals. (terminal_dock_widget::interpreter_output, terminal_dock_widget::update_prompt): New slots. (terminal_dock_widget::update_prompt_signal, terminal_dock_widget::interpreter_output_signal): New signals. * event-manager.h, event-manager.cc (Fdesktop): New function. (interpreter_events::start_gui, interpreter_events::close_gui, interpreter_events::update_prompt): New virtual functions. (interpreter_events::confirm_shutdown): Return true. (event_manager::start_gui, event_manager::close_gui, event_manager::update_prompt): New functions. * interpreter.h, interpreter.cc (interpreter::initialize): If using new terminal widget, only display startup message if not initially starting GUI. (class cli_input_reader): New class. (interpreter::experimental_terminal_widget, interpreter::get_line_and_eval): New functions. (interpreter::execute): If using new terminal widget, start GUI or command line reader and then enter server loop. * pt-eval.cc (tree_evaluator::server_loop): Reset parser at top of loop. also catch exit exception.
author John W. Eaton <jwe@octave.org>
date Thu, 25 Mar 2021 23:06:40 -0400
parents 76fdbe78884f
children d57c1d781093
files libgui/src/command-widget.cc libgui/src/command-widget.h libgui/src/interpreter-qobject.cc libgui/src/interpreter-qobject.h libgui/src/main-window.cc libgui/src/main-window.h libgui/src/module.mk libgui/src/octave-qobject.cc libgui/src/octave-qobject.h libgui/src/qt-application.cc libgui/src/qt-interpreter-events.cc libgui/src/qt-interpreter-events.h libgui/src/terminal-dock-widget.cc libgui/src/terminal-dock-widget.h libinterp/corefcn/event-manager.cc libinterp/corefcn/event-manager.h libinterp/corefcn/interpreter.cc libinterp/corefcn/interpreter.h libinterp/octave.cc libinterp/octave.h libinterp/options-usage.h libinterp/parse-tree/pt-eval.cc src/main.in.cc
diffstat 23 files changed, 1017 insertions(+), 114 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/src/command-widget.cc	Thu Mar 25 23:06:40 2021 -0400
@@ -0,0 +1,143 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2021 The Octave Project Developers
+//
+// See the file COPYRIGHT.md in the top-level directory of this
+// distribution or <https://octave.org/copyright/>.
+//
+// This file is part of Octave.
+//
+// Octave is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Octave 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Octave; see the file COPYING.  If not, see
+// <https://www.gnu.org/licenses/>.
+//
+////////////////////////////////////////////////////////////////////////
+
+#if defined (HAVE_CONFIG_H)
+#  include "config.h"
+#endif
+
+#include <QGroupBox>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QLineEdit>
+#include <QPushButton>
+#include <QTextBrowser>
+#include <QVBoxLayout>
+
+#include "command-widget.h"
+
+#include "cmd-edit.h"
+#include "event-manager.h"
+#include "input.h"
+#include "interpreter.h"
+
+namespace octave
+{
+  // FIXME: this class needs a different name and should probably be
+  // defined in a separate file.
+
+  command_widget::command_widget (base_qobject& oct_qobj, QWidget *p)
+    : QWidget (p), m_incomplete_parse (false),
+      m_prompt (new QLabel ("", this)),
+      m_line_edit (new QLineEdit (this)),
+      m_output_display (new QTextBrowser (this))
+  {
+    QPushButton *pause_button = new QPushButton (tr("Pause"), this);
+    QPushButton *stop_button = new QPushButton (tr("Stop"), this);
+    QPushButton *resume_button = new QPushButton (tr("Continue"), this);
+
+    QGroupBox *input_group_box = new QGroupBox (tr("Command Input"));
+    QHBoxLayout *input_layout = new QHBoxLayout;
+    input_layout->addWidget (m_prompt);
+    input_layout->addWidget (m_line_edit);
+    input_layout->addWidget (pause_button);
+    input_layout->addWidget (stop_button);
+    input_layout->addWidget (resume_button);
+    input_group_box->setLayout (input_layout);
+
+    QGroupBox *output_group_box = new QGroupBox (tr("Command Output"));
+    QHBoxLayout *output_layout = new QHBoxLayout ();
+    output_layout->addWidget (m_output_display);
+    output_group_box->setLayout (output_layout);
+
+    QVBoxLayout *main_layout = new QVBoxLayout ();
+    main_layout->addWidget (input_group_box);
+    main_layout->addWidget (output_group_box);
+
+    setLayout (main_layout);
+
+    connect (m_line_edit, SIGNAL (returnPressed (void)),
+             this, SLOT (accept_input_line (void)));
+
+    connect (this, SIGNAL (clear_line_edit (void)),
+             m_line_edit, SLOT (clear (void)));
+
+    connect (pause_button, SIGNAL (clicked (void)),
+             &oct_qobj, SLOT (interpreter_pause (void)));
+
+    connect (stop_button, SIGNAL (clicked (void)),
+             &oct_qobj, SLOT (interpreter_stop (void)));
+
+    connect (resume_button, SIGNAL (clicked (void)),
+             &oct_qobj, SLOT (interpreter_resume (void)));
+
+    connect (p, SIGNAL (update_prompt_signal (const QString&)),
+             m_prompt, SLOT (setText (const QString&)));
+
+    connect (p, SIGNAL (interpreter_output_signal (const QString&)),
+             this, SLOT (insert_interpreter_output (const QString&)));
+  }
+
+  void command_widget::insert_interpreter_output (const QString& msg)
+  {
+    QTextCursor cursor = m_output_display->textCursor ();
+
+    cursor.insertText (msg);
+
+    m_output_display->setTextCursor (cursor);
+  }
+
+  void command_widget::accept_input_line (void)
+  {
+    QTextCursor cursor = m_output_display->textCursor ();
+
+    QString input_line = m_line_edit->text ();
+
+    if (! m_incomplete_parse)
+      cursor.insertHtml ("<b>[in]:</b> ");
+    cursor.insertText (input_line);
+    cursor.insertHtml ("<br>");
+
+    m_output_display->setTextCursor (cursor);
+
+    emit interpreter_event
+      ([=] (interpreter& interp)
+       {
+         // INTERPRETER THREAD
+
+         interp.parse_and_execute (input_line.toStdString () + "\n",
+                                   m_incomplete_parse);
+
+         event_manager& evmgr = interp.get_event_manager ();
+         input_system& input_sys = interp.get_input_system ();
+
+         std::string prompt
+           = m_incomplete_parse ? input_sys.PS2 () : input_sys.PS1 ();
+
+         evmgr.update_prompt (command_editor::decode_prompt_string (prompt));
+       });
+
+    emit clear_line_edit ();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/src/command-widget.h	Thu Mar 25 23:06:40 2021 -0400
@@ -0,0 +1,72 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2021 The Octave Project Developers
+//
+// See the file COPYRIGHT.md in the top-level directory of this
+// distribution or <https://octave.org/copyright/>.
+//
+// This file is part of Octave.
+//
+// Octave is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Octave 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Octave; see the file COPYING.  If not, see
+// <https://www.gnu.org/licenses/>.
+//
+////////////////////////////////////////////////////////////////////////
+
+#if ! defined (octave_command_widget_h)
+#define octave_command_widget_h 1
+
+#include <QWidget>
+
+#include "octave-qobject.h"
+
+class QLabel;
+class QLineEdit;
+class QStrung;
+class QTextBrowser;
+
+namespace octave
+{
+  class base_qobject;
+
+  class command_widget : public QWidget
+  {
+    Q_OBJECT
+
+  public:
+
+    command_widget (base_qobject& oct_qobj, QWidget *p);
+
+  signals:
+
+    void clear_line_edit (void);
+
+    void interpreter_event (const fcn_callback& fcn);
+    void interpreter_event (const meth_callback& meth);
+
+  protected slots:
+
+    void accept_input_line (void);
+
+    void insert_interpreter_output (const QString& msg);
+
+  private:
+
+    bool m_incomplete_parse;
+    QLabel *m_prompt;
+    QLineEdit *m_line_edit;
+    QTextBrowser *m_output_display;
+  };
+}
+
+#endif
--- a/libgui/src/interpreter-qobject.cc	Thu Mar 25 17:47:56 2021 -0400
+++ b/libgui/src/interpreter-qobject.cc	Thu Mar 25 23:06:40 2021 -0400
@@ -66,7 +66,8 @@
 
         interp.initialize ();
 
-        if (app_context.start_gui_p ())
+        if (app_context.start_gui_p ()
+            && ! m_octave_qobj.experimental_terminal_widget ())
           {
             input_system& input_sys = interp.get_input_system ();
 
@@ -95,31 +96,40 @@
         exit_status = xe.exit_status ();
       }
 
-    // Signal that the interpreter is done executing code in the main
-    // REPL, from script files, or command line eval arguments.  By
-    // using a signal here, we give the GUI a chance to process any
-    // pending events, then signal that it is safe to shutdown the
-    // interpreter.  Our notification here allows the GUI to insert the
-    // request to shutdown the interpreter in the event queue after any
-    // other pending signals.  The application context owns the
-    // interpreter and will be responsible for deleting it later, when
-    // the application object destructor is executed.
+    if (m_octave_qobj.experimental_terminal_widget ())
+      {
+        interp.shutdown ();
 
-    emit execution_finished (exit_status);
-  }
+        emit shutdown_finished (exit_status);
+      }
+    else
+      {
+        // Signal that the interpreter is done executing code in the
+        // main REPL, from script files, or command line eval arguments.
+        // By using a signal here, we give the GUI a chance to process
+        // any pending events, then signal that it is safe to shutdown
+        // the interpreter.  Our notification here allows the GUI to
+        // insert the request to shutdown the interpreter in the event
+        // queue after any other pending signals.  The application
+        // context owns the interpreter and will be responsible for
+        // deleting it later, when the application object destructor is
+        // executed.
 
-  // This function is expected to be executed when the GUI signals that
-  // it is finished processing events and ready for the interpreter to
-  // perform shutdown actions.
+        emit execution_finished (exit_status);
+      }
+  }
 
   void interpreter_qobject::shutdown (int exit_status)
   {
-    if (m_interpreter)
-      m_interpreter->shutdown ();
+    if (! m_octave_qobj.experimental_terminal_widget ())
+      {
+        if (m_interpreter)
+          m_interpreter->shutdown ();
 
-    // Signal that the interpreter has executed shutdown actions.
+        // Signal that the interpreter has executed shutdown actions.
 
-    emit shutdown_finished (exit_status);
+        emit shutdown_finished (exit_status);
+      }
   }
 
   void interpreter_qobject::interpreter_event (const fcn_callback& fcn)
@@ -147,9 +157,66 @@
     if (! m_interpreter)
       return;
 
+    // The following is a direct function call across threads.
+    // We need to ensure that it uses thread-safe functions.
+
     m_interpreter->interrupt ();
   }
 
+  void interpreter_qobject::pause (void)
+  {
+    // FIXME: Should we make this action work with the old terminal
+    // widget?
+
+    if (m_octave_qobj.experimental_terminal_widget ())
+      {
+        if (! m_interpreter)
+          return;
+
+        // The following is a direct function call across threads.
+        // We need to ensure that it uses thread-safe functions.
+
+        m_interpreter->pause ();
+      }
+  }
+
+  void interpreter_qobject::stop (void)
+  {
+    // FIXME: Should we make this action work with the old terminal
+    // widget?
+
+    if (m_octave_qobj.experimental_terminal_widget ())
+      {
+        if (! m_interpreter)
+          return;
+
+        // The following is a direct function call across threads.
+        // We need to ensure that it uses thread-safe functions.
+
+        m_interpreter->stop ();
+      }
+  }
+
+  void interpreter_qobject::resume (void)
+  {
+    // FIXME: Should we make this action work with the old terminal
+    // widget?
+
+    if (m_octave_qobj.experimental_terminal_widget ())
+      {
+        // FIXME: This action should only be available when the
+        // interpreter is paused.
+
+        interpreter_event
+          ([=] (interpreter& interp)
+          {
+            // INTERPRETER THREAD
+
+            interp.resume ();
+          });
+      }
+  }
+
   qt_interpreter_events * interpreter_qobject::qt_link (void)
   {
     return m_octave_qobj.qt_link ();
--- a/libgui/src/interpreter-qobject.h	Thu Mar 25 17:47:56 2021 -0400
+++ b/libgui/src/interpreter-qobject.h	Thu Mar 25 23:06:40 2021 -0400
@@ -54,12 +54,20 @@
 
     void interrupt (void);
 
+    // Note: PAUSE, STOP, and RESUME are currently only used by the new
+    // experimental terminal widget.
+    void pause (void);
+    void stop (void);
+    void resume (void);
+
   signals:
 
     void ready (void);
 
     void execution_finished (int);
 
+    // Note: SHUTDOWN_FINISHED is currently only used by the new
+    // experimental terminal widget.
     void shutdown_finished (int);
 
   public slots:
@@ -86,6 +94,8 @@
 
     void execute (void);
 
+    // Note: SHUTDOWN is currently only used by the new experimental
+    // terminal widget.
     void shutdown (int);
 
   private:
--- a/libgui/src/main-window.cc	Thu Mar 25 17:47:56 2021 -0400
+++ b/libgui/src/main-window.cc	Thu Mar 25 23:06:40 2021 -0400
@@ -1722,6 +1722,25 @@
 #endif
       }
 
+    if (m_octave_qobj.experimental_terminal_widget ())
+      {
+        // Set initial prompt.
+
+        emit interpreter_event
+          ([] (interpreter& interp)
+          {
+            // INTERPRETER_THREAD
+
+            event_manager& evmgr = interp.get_event_manager ();
+            input_system& input_sys = interp.get_input_system ();
+
+            input_sys.PS1 (">> ");
+            std::string prompt = input_sys.PS1 ();
+
+            evmgr.update_prompt (command_editor::decode_prompt_string (prompt));
+          });
+      }
+
     focus_command_window ();  // make sure that the command window has focus
   }
 
@@ -1962,13 +1981,18 @@
 
         e->ignore ();
 
-        emit interpreter_event
-          ([] (interpreter& interp)
-           {
-             // INTERPRETER THREAD
-
-             interp.quit (0, false, false);
-           });
+        if (m_octave_qobj.experimental_terminal_widget ())
+          emit close_gui_signal ();
+        else
+          {
+            emit interpreter_event
+              ([] (interpreter& interp)
+               {
+                 // INTERPRETER THREAD
+
+                 interp.quit (0, false, false);
+               });
+          }
       }
     else
       e->ignore ();
@@ -2162,6 +2186,16 @@
     connect (qt_link, SIGNAL (apply_new_settings (void)),
              this, SLOT (request_reload_settings (void)));
 
+    if (m_octave_qobj.experimental_terminal_widget ())
+      {
+        connect (qt_link, SIGNAL (interpreter_output_signal (const QString&)),
+                 m_command_window,
+                 SLOT (interpreter_output (const QString&)));
+
+        connect (qt_link, SIGNAL (update_prompt_signal (const QString&)),
+                 m_command_window, SLOT (update_prompt (const QString&)));
+      }
+
     connect (qt_link,
              SIGNAL (set_workspace_signal (bool, bool, const symbol_info_list&)),
              m_workspace_model,
--- a/libgui/src/main-window.h	Thu Mar 25 17:47:56 2021 -0400
+++ b/libgui/src/main-window.h	Thu Mar 25 23:06:40 2021 -0400
@@ -94,6 +94,10 @@
 
   signals:
 
+    // Note: CLOSE_GUI_SIGNAL is currently only used by the new
+    // experimental terminal widget.
+    void close_gui_signal (void);
+
     void active_dock_changed (octave_dock_widget *, octave_dock_widget *);
     void editor_focus_changed (bool);
 
--- a/libgui/src/module.mk	Thu Mar 25 17:47:56 2021 -0400
+++ b/libgui/src/module.mk	Thu Mar 25 23:06:40 2021 -0400
@@ -134,6 +134,7 @@
 
 OCTAVE_GUI_SRC_MOC = \
   %reldir%/moc-external-editor-interface.cc \
+  %reldir%/moc-command-widget.cc \
   %reldir%/moc-dialog.cc \
   %reldir%/moc-documentation-dock-widget.cc \
   %reldir%/moc-documentation.cc \
@@ -186,6 +187,7 @@
 BUILT_SOURCES += $(octave_gui_UI_H)
 
 noinst_HEADERS += \
+  %reldir%/command-widget.h \
   %reldir%/dialog.h \
   %reldir%/octave-dock-widget.h \
   %reldir%/documentation-dock-widget.h \
@@ -249,6 +251,7 @@
 
 
 %canon_reldir%_%canon_reldir%_la_SOURCES = \
+  %reldir%/command-widget.cc \
   %reldir%/dialog.cc \
   %reldir%/documentation-dock-widget.cc \
   %reldir%/documentation.cc \
--- a/libgui/src/octave-qobject.cc	Thu Mar 25 17:47:56 2021 -0400
+++ b/libgui/src/octave-qobject.cc	Thu Mar 25 23:06:40 2021 -0400
@@ -51,6 +51,7 @@
 #  include <objc/message.h>
 #endif
 
+#include "interpreter.h"
 #include "oct-env.h"
 #include "version.h"
 
@@ -164,8 +165,10 @@
       m_qsci_tr (new QTranslator ()), m_translators_installed (false),
       m_qt_interpreter_events (new qt_interpreter_events (*this)),
       m_interpreter_qobj (new interpreter_qobject (*this)),
-      m_main_thread (new QThread ()), m_gui_app (gui_app),
-      m_main_window (nullptr), m_interpreter_ready (false)
+      m_main_thread (new QThread ()),
+      m_gui_app (gui_app),
+      m_interpreter_ready (false),
+      m_main_window (nullptr)
   {
     std::string show_gui_msgs =
       sys::env::getenv ("OCTAVE_SHOW_GUI_MESSAGES");
@@ -201,11 +204,14 @@
     // Force left-to-right alignment (see bug #46204)
     m_qapplication->setLayoutDirection (Qt::LeftToRight);
 
-    connect (m_interpreter_qobj, SIGNAL (execution_finished (int)),
-             this, SLOT (handle_interpreter_execution_finished (int)));
+    if (! m_app_context.experimental_terminal_widget ())
+      {
+        connect (m_interpreter_qobj, SIGNAL (execution_finished (int)),
+                 this, SLOT (handle_interpreter_execution_finished (int)));
 
-    connect (this, SIGNAL (request_interpreter_shutdown (int)),
-             m_interpreter_qobj, SLOT (shutdown (int)));
+        connect (this, SIGNAL (request_interpreter_shutdown (int)),
+                 m_interpreter_qobj, SLOT (shutdown (int)));
+      }
 
     connect (m_interpreter_qobj, SIGNAL (shutdown_finished (int)),
              this, SLOT (handle_interpreter_shutdown_finished (int)));
@@ -222,24 +228,17 @@
     connect (m_qapplication, SIGNAL (interpreter_event (const meth_callback&)),
              this, SLOT (interpreter_event (const meth_callback&)));
 
+    if (m_app_context.experimental_terminal_widget ())
+      {
+        connect (qt_link (), SIGNAL (start_gui_signal (bool)),
+                 this, SLOT (start_gui (bool)));
+      }
+
     connect (qt_link (),
              SIGNAL (copy_image_to_clipboard_signal (const QString&, bool)),
              this, SLOT (copy_image_to_clipboard (const QString&, bool)));
 
-    if (gui_app)
-      {
-        m_main_window = new main_window (*this);
-
-        if (m_interpreter_ready)
-          m_main_window->handle_octave_ready ();
-
-        connect (qt_link (),
-                 SIGNAL (focus_window_signal (const QString&)),
-                 m_main_window, SLOT (focus_window (const QString&)));
-
-        m_app_context.gui_running (true);
-      }
-    else
+    if (m_app_context.experimental_terminal_widget ())
       {
         // Get settings file.
         m_resource_manager.reload_settings ();
@@ -249,6 +248,35 @@
 
         m_qapplication->setQuitOnLastWindowClosed (false);
       }
+    else
+      {
+        if (gui_app)
+          {
+            m_main_window = new main_window (*this);
+
+            if (m_interpreter_ready)
+              m_main_window->handle_octave_ready ();
+            else
+              connect (m_interpreter_qobj, SIGNAL (ready ()),
+                       m_main_window, SLOT (handle_octave_ready ()));
+
+            connect (qt_link (),
+                     SIGNAL (focus_window_signal (const QString&)),
+                     m_main_window, SLOT (focus_window (const QString&)));
+
+            m_app_context.gui_running (true);
+          }
+        else
+          {
+            // Get settings file.
+            m_resource_manager.reload_settings ();
+
+            // After settings.
+            config_translators ();
+
+            m_qapplication->setQuitOnLastWindowClosed (false);
+          }
+      }
 
     start_main_thread ();
   }
@@ -271,17 +299,6 @@
     string_vector::delete_c_str_vec (m_argv);
   }
 
-  void base_qobject::interpreter_ready (void)
-  {
-    // Slot for signal of interpreter being ready.
-    // If main window already exists, call initialization,
-    // otherwise store interpreter state
-    if (m_main_window)
-      m_main_window->handle_octave_ready ();
-    else
-      m_interpreter_ready = true;
-  }
-
   void base_qobject::config_translators (void)
   {
     if (m_translators_installed)
@@ -298,8 +315,14 @@
 
   void base_qobject::start_main_thread (void)
   {
-    // Defer initializing and executing the interpreter until after the main
-    // window and QApplication are running to prevent race conditions
+    // Note: if using the new experimental terminal widget, we defer
+    // initializing and executing the interpreter until the main event
+    // loop begins executing.
+
+    // With the old terminal widget, we defer initializing and executing
+    // the interpreter until after the main window and QApplication are
+    // running to prevent race conditions.
+
     QTimer::singleShot (0, m_interpreter_qobj, SLOT (execute (void)));
 
     m_interpreter_qobj->moveToThread (m_main_thread);
@@ -309,7 +332,31 @@
 
   int base_qobject::exec (void)
   {
-    return m_qapplication->exec ();
+    int status;
+
+    if (m_app_context.experimental_terminal_widget ())
+      {
+        status = m_qapplication->exec ();
+
+        m_main_thread->quit ();
+        m_main_thread->wait ();
+      }
+    else
+      status = m_qapplication->exec ();
+
+    return status;
+  }
+
+  // Provided for convenience.  Will be removed once we eliminate the
+  // old terminal widget.
+  bool base_qobject::experimental_terminal_widget (void) const
+  {
+    return m_app_context.experimental_terminal_widget ();
+  }
+
+  bool base_qobject::gui_running (void) const
+  {
+    return m_app_context.gui_running ();
   }
 
   bool base_qobject::confirm_shutdown (void)
@@ -321,9 +368,88 @@
     return m_main_window ? m_main_window->confirm_shutdown () : true;
   }
 
+  void base_qobject::start_gui (bool gui_app)
+  {
+    if (m_app_context.experimental_terminal_widget ())
+      {
+        if (m_main_window)
+          return;
+
+        m_gui_app = gui_app;
+
+        m_main_window = new main_window (*this);
+
+        connect (qt_link (),
+                 SIGNAL (focus_window_signal (const QString&)),
+                 m_main_window, SLOT (focus_window (const QString&)));
+
+        connect (qt_link (), SIGNAL (close_gui_signal ()),
+                 this, SLOT (close_gui ()));
+
+        connect (m_main_window, SIGNAL (close_gui_signal ()),
+                 this, SLOT (close_gui ()));
+
+        if (m_interpreter_ready)
+          m_main_window->handle_octave_ready ();
+        else
+          connect (m_interpreter_qobj, SIGNAL (ready ()),
+                   m_main_window, SLOT (handle_octave_ready ()));
+
+        if (m_gui_app)
+          m_qapplication->setQuitOnLastWindowClosed (true);
+        else
+          {
+            // FIXME: Save current values of PS1 and PS2 so they can be
+            // restored when we return to the command line?
+          }
+
+        m_app_context.gui_running (true);
+      }
+  }
+
+  void base_qobject::close_gui (void)
+  {
+    if (m_app_context.experimental_terminal_widget ())
+      {
+        if (! m_main_window)
+          return;
+
+        // FIXME: Restore previous values of PS1 and PS2 if we are
+        // returning to the command line?
+
+        interpreter_event
+          ([=] (interpreter& interp)
+          {
+            // INTERPRETER THREAD
+
+            application *app = interp.get_app_context ();
+
+            cmdline_options opts = app->options ();
+
+            if (opts.gui ())
+              interp.quit (0, false, false);
+          });
+
+        m_app_context.gui_running (false);
+
+        if (m_main_window)
+          {
+            m_main_window->deleteLater ();
+
+            m_main_window = nullptr;
+          }
+      }
+  }
+
   void base_qobject::handle_interpreter_execution_finished (int exit_status)
   {
-    emit request_interpreter_shutdown (exit_status);
+    if (! m_app_context.experimental_terminal_widget ())
+      emit request_interpreter_shutdown (exit_status);
+  }
+
+  void base_qobject::interpreter_ready (void)
+  {
+    m_interpreter_ready = true;
   }
 
   void base_qobject::handle_interpreter_shutdown_finished (int exit_status)
@@ -365,10 +491,28 @@
 
   void base_qobject::interpreter_interrupt (void)
   {
-    // The following is a direct function call across threads.  It works
-    // because the it is accessing a thread-safe function.
+    m_interpreter_qobj->interrupt ();
+  }
+
+  // FIXME: Should we try to make the pause, stop, and resume actions
+  // work for both the old and new terminal widget?
 
-    m_interpreter_qobj->interrupt ();
+  void base_qobject::interpreter_pause (void)
+  {
+    if (m_app_context.experimental_terminal_widget ())
+      m_interpreter_qobj->pause ();
+  }
+
+  void base_qobject::interpreter_stop (void)
+  {
+    if (m_app_context.experimental_terminal_widget ())
+      m_interpreter_qobj->stop ();
+  }
+
+  void base_qobject::interpreter_resume (void)
+  {
+    if (m_app_context.experimental_terminal_widget ())
+      m_interpreter_qobj->resume ();
   }
 
   void base_qobject::copy_image_to_clipboard (const QString& file,
--- a/libgui/src/octave-qobject.h	Thu Mar 25 17:47:56 2021 -0400
+++ b/libgui/src/octave-qobject.h	Thu Mar 25 23:06:40 2021 -0400
@@ -83,6 +83,8 @@
 
   public:
 
+    // Note: the GUI_APP argument is not needed with the new
+    // experimental terminal widget.
     base_qobject (qt_application& app_context, bool gui_app = false);
 
     ~base_qobject (void);
@@ -99,6 +101,13 @@
     // The Qt QApplication.
     QApplication * qapplication (void) { return m_qapplication; };
 
+    // Provided for convenience.  Will be removed once we eliminate the
+    // old terminal widget.
+    bool experimental_terminal_widget (void) const;
+
+    // Provided for convenience.
+    bool gui_running (void) const;
+
     resource_manager& get_resource_manager (void)
     {
       return m_resource_manager;
@@ -142,9 +151,15 @@
 
   public slots:
 
-    void interpreter_ready (void);
+    // Note: START_GUI and CLOSE_GUI don't currently perform any work
+    // with the old terminal widget and
+    // HANDLE_INTERPRETER_EXECUTION_FINISHED doesn't perform any action
+    // with the new experimental terminal widget.
+    void start_gui (bool gui_app);
+    void close_gui (void);
+    void handle_interpreter_execution_finished (int);
 
-    void handle_interpreter_execution_finished (int);
+    void interpreter_ready (void);
 
     void handle_interpreter_shutdown_finished (int);
 
@@ -154,6 +169,12 @@
 
     void interpreter_interrupt (void);
 
+    // Note: these currently only work with the new experimental
+    // terminal widget.
+    void interpreter_pause (void);
+    void interpreter_stop (void);
+    void interpreter_resume (void);
+
     void copy_image_to_clipboard (const QString& file, bool remove_file);
 
   protected:
@@ -188,9 +209,9 @@
 
     bool m_gui_app;
 
-    main_window *m_main_window;
+    bool m_interpreter_ready;
 
-    bool m_interpreter_ready;
+    main_window *m_main_window;
   };
 }
 
--- a/libgui/src/qt-application.cc	Thu Mar 25 17:47:56 2021 -0400
+++ b/libgui/src/qt-application.cc	Thu Mar 25 23:06:40 2021 -0400
@@ -51,7 +51,10 @@
 
   bool qt_application::start_gui_p (void) const
   {
-    return m_options.gui ();
+    // Note: this function is not needed if using the experimental
+    // terminal widget, so return a dummy value of false in that case.
+
+    return experimental_terminal_widget () ? false : m_options.gui ();
   }
 
   int qt_application::execute (void)
@@ -62,6 +65,9 @@
 
     // Create and show main window.
 
+    // Note: the second argument is ignored if using the new terminal
+    // widget.
+
     base_qobject qt_interface (*this, start_gui_p ());
 
     return qt_interface.exec ();
--- a/libgui/src/qt-interpreter-events.cc	Thu Mar 25 17:47:56 2021 -0400
+++ b/libgui/src/qt-interpreter-events.cc	Thu Mar 25 23:06:40 2021 -0400
@@ -27,6 +27,9 @@
 #  include "config.h"
 #endif
 
+#include <iostream>
+#include <sstream>
+
 #include <QDialog>
 #include <QDir>
 #include <QIcon>
@@ -130,6 +133,18 @@
              SLOT (gui_preference_slot (const QString&, const QString&)));
   }
 
+  void qt_interpreter_events::start_gui (bool gui_app)
+  {
+    if (m_octave_qobj.experimental_terminal_widget ())
+      emit start_gui_signal (gui_app);
+  }
+
+  void qt_interpreter_events::close_gui (void)
+  {
+    if (m_octave_qobj.experimental_terminal_widget ())
+      emit close_gui_signal ();
+  }
+
   std::list<std::string>
   qt_interpreter_events::file_dialog (const filter_list& filter,
                                       const std::string& title,
@@ -447,6 +462,37 @@
     emit unregister_doc_signal (QString::fromStdString (file));
   }
 
+  void qt_interpreter_events::interpreter_output (const std::string& msg)
+  {
+    if (m_octave_qobj.experimental_terminal_widget ()
+        && m_octave_qobj.gui_running ())
+      emit interpreter_output_signal (QString::fromStdString (msg));
+    else
+      {
+        // FIXME: is this the correct thing to do?
+        std::cout << msg;
+      }
+  }
+
+  void qt_interpreter_events::display_exception (const execution_exception& ee,
+                                                 bool beep)
+  {
+    if (m_octave_qobj.experimental_terminal_widget ()
+        && m_octave_qobj.gui_running ())
+      {
+        std::ostringstream buf;
+        ee.display (buf);
+        emit interpreter_output_signal (QString::fromStdString (buf.str ()));
+      }
+    else
+      {
+        if (beep)
+          std::cerr << "\a";
+
+        ee.display (std::cerr);
+      }
+  }
+
   void qt_interpreter_events::gui_status_update (const std::string& feature,
                                                  const std::string& status)
   {
@@ -500,6 +546,11 @@
     emit clear_workspace_signal ();
   }
 
+  void qt_interpreter_events::update_prompt (const std::string& prompt)
+  {
+    emit update_prompt_signal (QString::fromStdString (prompt));
+  }
+
   void qt_interpreter_events::set_history (const string_vector& hist)
   {
     QStringList qt_hist;
--- a/libgui/src/qt-interpreter-events.h	Thu Mar 25 17:47:56 2021 -0400
+++ b/libgui/src/qt-interpreter-events.h	Thu Mar 25 23:06:40 2021 -0400
@@ -81,6 +81,11 @@
 
     ~qt_interpreter_events (void) = default;
 
+    // Note: these functions currently do nothing with the old terminal
+    // widget.
+    void start_gui (bool gui_app = false);
+    void close_gui (void);
+
     std::list<std::string>
     file_dialog (const filter_list& filter, const std::string& title,
                  const std::string& filename, const std::string& pathname,
@@ -141,6 +146,12 @@
 
     void unregister_doc (const std::string& file);
 
+    // Note: this function currently does nothing with the old terminal
+    // widget.
+    void interpreter_output (const std::string& msg);
+
+    void display_exception (const execution_exception& ee, bool beep);
+
     void gui_status_update (const std::string& feature, const std::string& status);
 
     void update_gui_lexer (void);
@@ -158,6 +169,8 @@
 
     void clear_workspace (void);
 
+    void update_prompt (const std::string& prompt);
+
     void set_history (const string_vector& hist);
 
     void append_history (const std::string& hist_entry);
@@ -196,6 +209,10 @@
 
   signals:
 
+    // Note: these signals are not currently used by the old terminal widget.
+    void start_gui_signal (bool gui_app);
+    void close_gui_signal (void);
+
     void copy_image_to_clipboard_signal (const QString& file, bool remove_file);
 
     void focus_window_signal (const QString& win_name);
@@ -217,6 +234,8 @@
 
     void clear_workspace_signal (void);
 
+    void update_prompt_signal (const QString& prompt);
+
     void set_history_signal (const QStringList& hist);
 
     void append_history_signal (const QString& hist_entry);
@@ -244,6 +263,9 @@
 
     void unregister_doc_signal (const QString& file);
 
+    // Note: this signal currently not used by the old terminal widget.
+    void interpreter_output_signal (const QString& msg);
+
     void gui_status_update_signal (const QString& feature, const QString& status);
 
     void update_gui_lexer_signal (bool update_apis_only);
--- a/libgui/src/terminal-dock-widget.cc	Thu Mar 25 17:47:56 2021 -0400
+++ b/libgui/src/terminal-dock-widget.cc	Thu Mar 25 23:06:40 2021 -0400
@@ -29,23 +29,32 @@
 
 #include <QDesktopWidget>
 
+// This header is only needed for the new terminal widget.
+#include "command-widget.h"
+
+// This header is only needed for the old terminal widget.
+#include "QTerminal.h"
+
 #include "gui-preferences-cs.h"
 #include "gui-preferences-global.h"
+
 #include "octave-qobject.h"
 #include "terminal-dock-widget.h"
 
-#include "quit.h"
-#include "signal-wrappers.h"
-
-#include "sighandlers.h"
-
 namespace octave
 {
   terminal_dock_widget::terminal_dock_widget (QWidget *p,
                                               base_qobject& oct_qobj)
     : octave_dock_widget ("TerminalDockWidget", p, oct_qobj),
-      m_terminal (QTerminal::create (oct_qobj, this, p))
+      m_experimental_terminal_widget (oct_qobj.experimental_terminal_widget ())
   {
+    // FIXME: we could do this in a better way, but improving it doesn't
+    // matter much if we will eventually be removing the old terminal.
+    if (m_experimental_terminal_widget)
+      m_terminal = new command_widget (oct_qobj, this);
+    else
+      m_terminal = QTerminal::create (oct_qobj, this, p);
+
     m_terminal->setObjectName ("OctaveTerminal");
     m_terminal->setFocusPolicy (Qt::StrongFocus);
 
@@ -55,12 +64,24 @@
     setWidget (m_terminal);
     setFocusProxy (m_terminal);
 
-    connect (m_terminal, SIGNAL (interrupt_signal (void)),
-             &oct_qobj, SLOT (interpreter_interrupt (void)));
+    if (m_experimental_terminal_widget)
+      {
+        // Any interpreter_event signal from the terminal widget is
+        // handled the same as for the parent terminal dock widget.
+
+        connect (m_terminal, SIGNAL (interpreter_event (const fcn_callback&)),
+                 this, SIGNAL (interpreter_event (const fcn_callback&)));
 
-    // Connect the visibility signal to the terminal for dis-/enabling timers
-    connect (this, SIGNAL (visibilityChanged (bool)),
-             m_terminal, SLOT (handle_visibility_changed (bool)));
+        connect (m_terminal, SIGNAL (interpreter_event (const meth_callback&)),
+                 this, SIGNAL (interpreter_event (const meth_callback&)));
+      }
+    else
+      {
+        // Connect the visibility signal to the terminal for
+        // dis-/enabling timers.
+        connect (this, SIGNAL (visibilityChanged (bool)),
+                 m_terminal, SLOT (handle_visibility_changed (bool)));
+      }
 
     // Chose a reasonable size at startup in order to avoid truncated
     // startup messages
@@ -96,7 +117,18 @@
   bool terminal_dock_widget::has_focus (void) const
   {
     QWidget *w = widget ();
-
     return w->hasFocus ();
   }
+
+  void terminal_dock_widget::interpreter_output (const QString& msg)
+  {
+    if (m_experimental_terminal_widget)
+      emit interpreter_output_signal (msg);
+  }
+
+  void terminal_dock_widget::update_prompt (const QString& prompt)
+  {
+    if (m_experimental_terminal_widget)
+      emit update_prompt_signal (prompt);
+  }
 }
--- a/libgui/src/terminal-dock-widget.h	Thu Mar 25 17:47:56 2021 -0400
+++ b/libgui/src/terminal-dock-widget.h	Thu Mar 25 23:06:40 2021 -0400
@@ -28,8 +28,6 @@
 
 #include <QString>
 
-#include "QTerminal.h"
-
 #include "octave-dock-widget.h"
 
 namespace octave
@@ -48,9 +46,30 @@
 
     bool has_focus (void) const;
 
+  signals:
+
+    // Note: UPDATE_PROMPT_SIGNAL and INTERPRETER_OUTPUT_SIGNAL are
+    // currently only used by the new experimental terminal widget.
+
+    void update_prompt_signal (const QString&);
+
+    void interpreter_output_signal (const QString&);
+
+  public slots:
+
+    // Note: INTERPRETER_OUTPUT and UPDATE_PROMPT are currently only
+    // used by the new experimental terminal widget.
+
+    void interpreter_output (const QString&);
+
+    void update_prompt (const QString&);
+
   private:
 
-    QTerminal *m_terminal;
+    bool m_experimental_terminal_widget;
+
+    // FIXME!!!  Maybe my_term should just be derived from QTerminal?
+    QWidget *m_terminal;
   };
 }
 
--- a/libinterp/corefcn/event-manager.cc	Thu Mar 25 17:47:56 2021 -0400
+++ b/libinterp/corefcn/event-manager.cc	Thu Mar 25 23:06:40 2021 -0400
@@ -182,6 +182,40 @@
   }
 }
 
+// FIXME: Should the following function be __event_manager_desktop__
+// with the desktop function implemented in a .m file, similar to the
+// way the UI* functions work?
+
+DEFMETHOD (desktop, interp, , ,
+           doc: /* -*- texinfo -*-
+@deftypefn {} {} desktop ()
+If running in command-line mode, start the GUI desktop.
+@end deftypefn */)
+{
+  if (interp.experimental_terminal_widget ())
+    {
+      if (! octave::application::is_gui_running ())
+        {
+          // FIXME: Currently, the following action is queued and
+          // executed in a Qt event loop and we return immediately to
+          // the command prompt where additional commands may be
+          // executed.  Is that what should happen?  Or should we be
+          // waiting until the GUI exits to return to the command
+          // prompt, similar to the way the UI* functions work?
+
+          octave::event_manager& evmgr = interp.get_event_manager ();
+
+          evmgr.start_gui ();
+        }
+      else
+        warning ("GUI desktop is already running");
+    }
+  else
+    error ("desktop function requires new experimental terminal widget");
+
+  return ovl ();
+}
+
 DEFMETHOD (__event_manager_enabled__, interp, , ,
            doc: /* -*- texinfo -*-
 @deftypefn {} {} __event_manager_enabled__ ()
--- a/libinterp/corefcn/event-manager.h	Thu Mar 25 17:47:56 2021 -0400
+++ b/libinterp/corefcn/event-manager.h	Thu Mar 25 23:06:40 2021 -0400
@@ -87,6 +87,17 @@
 
     virtual ~interpreter_events (void) = default;
 
+    // Note: START_GUI and CLOSE_GUI currently only work with the new
+    // experimental terminal widget.
+
+    // Set GUI_APP to true when starting Octave as a gui application
+    // (invoked with the --gui option) and false when starting the GUI
+    // from the Octave prompt when Octave is already running as a
+    // command line application.
+
+    virtual void start_gui (bool /*gui_app*/ = false) { }
+    virtual void close_gui (void) { }
+
     // Dialogs.
 
     typedef std::list<std::pair<std::string, std::string>> filter_list;
@@ -149,7 +160,7 @@
     // confirmation before another action.  Could these be reformulated
     // using the question_dialog action?
 
-    virtual bool confirm_shutdown (void) { return false; }
+    virtual bool confirm_shutdown (void) { return true; }
 
     virtual bool prompt_new_edit_file (const std::string& /*file*/)
     {
@@ -199,8 +210,7 @@
 
     virtual void interpreter_output (const std::string& /*msg*/) { }
 
-    virtual void display_exception (const execution_exception& ee,
-                                    bool beep = false);
+    virtual void display_exception (const execution_exception& ee, bool beep);
 
     virtual void gui_status_update (const std::string& /*feature*/,
                                     const std::string& /*status*/) { }
@@ -226,6 +236,8 @@
 
     virtual void clear_workspace (void) { }
 
+    virtual void update_prompt (const std::string& /*prompt*/) { }
+
     virtual void set_history (const string_vector& /*hist*/) { }
 
     virtual void append_history (const std::string& /*hist_entry*/) { }
@@ -323,6 +335,22 @@
     // Please keep this list of declarations in the same order as the
     // ones above in the interpreter_events class.
 
+
+    // Note: START_GUI and CLOSE_GUI currently only work with the new
+    // experimental terminal object.
+
+    void start_gui (bool gui_app = false)
+    {
+      if (enabled ())
+        instance->start_gui (gui_app);
+    }
+
+    void close_gui (void)
+    {
+      if (enabled ())
+        instance->close_gui ();
+    }
+
     typedef std::list<std::pair<std::string, std::string>> filter_list;
 
     std::list<std::string>
@@ -585,6 +613,12 @@
         instance->clear_workspace ();
     }
 
+    void update_prompt (const std::string& prompt)
+    {
+      if (enabled ())
+        instance->update_prompt (prompt);
+    }
+
     void set_history (const string_vector& hist)
     {
       if (enabled ())
--- a/libinterp/corefcn/interpreter.cc	Thu Mar 25 17:47:56 2021 -0400
+++ b/libinterp/corefcn/interpreter.cc	Thu Mar 25 23:06:40 2021 -0400
@@ -33,6 +33,12 @@
 #include <string>
 #include <iostream>
 
+// The following headers are only needed for the new experimental
+// terminal widget.
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+
 #include "cmd-edit.h"
 #include "cmd-hist.h"
 #include "file-ops.h"
@@ -724,7 +730,15 @@
     if (m_initialized)
       return;
 
-    display_startup_message ();
+    const cmdline_options& options = m_app_context->options ();
+
+    if (options.experimental_terminal_widget ())
+      {
+        if (! options.gui ())
+          display_startup_message ();
+      }
+    else
+      display_startup_message ();
 
     // Wait to read the history file until the interpreter reads input
     // files and begins evaluating commands.
@@ -756,6 +770,131 @@
     m_initialized = true;
   }
 
+  // Note: this function is currently only used with the new
+  // experimental terminal widget.
+
+  void interpreter::get_line_and_eval (void)
+  {
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lock (mtx);
+    std::condition_variable cv;
+    bool incomplete_parse = false;
+    bool evaluation_pending = false;
+    bool exiting = false;
+
+    while (true)
+      {
+        // FIXME: Detect EOF?  Use readline?  If
+        // so, then we need to disable idle event loop hook function
+        // execution.
+
+        std::string ps
+          = incomplete_parse ? m_input_system.PS2 () : m_input_system.PS1 ();
+
+        std::cout << command_editor::decode_prompt_string (ps);
+
+        std::string input;
+        std::getline (std::cin, input);
+
+        if (input.empty ())
+          continue;
+
+        incomplete_parse = false;
+        evaluation_pending = true;
+        exiting = false;
+
+        m_event_manager.post_event
+          ([&] (interpreter& interp)
+           {
+             // INTERPRETER THREAD
+
+             std::lock_guard<std::mutex> local_lock (mtx);
+
+             try
+               {
+                 interp.parse_and_execute (input, incomplete_parse);
+               }
+             catch (const exit_exception&)
+               {
+                 evaluation_pending = false;
+                 exiting = true;
+                 cv.notify_all ();
+                 throw;
+               }
+             catch (const execution_exception& ee)
+               {
+                 m_error_system.save_exception (ee);
+                 m_error_system.display_exception (ee);
+
+                 if (m_interactive)
+                   {
+                     recover_from_exception ();
+                     evaluation_pending = false;
+                     cv.notify_all ();
+                   }
+                 else
+                   {
+                     evaluation_pending = false;
+                     cv.notify_all ();
+                     throw exit_exception (1);
+                   }
+               }
+             catch (...)
+               {
+                 evaluation_pending = false;
+                 cv.notify_all ();
+                 throw;
+               }
+
+             evaluation_pending = false;
+             cv.notify_all ();
+           });
+
+        // Wait until evaluation is finished before prompting for input
+        // again.
+
+        cv.wait (lock, [&] { return ! evaluation_pending; });
+
+        if (exiting)
+          break;
+      }
+  }
+
+  // Note: the following class is currently only used with the new
+  // experimental terminal widget.
+
+  class cli_input_reader
+  {
+  public:
+
+    cli_input_reader (interpreter& interp)
+      : m_interpreter (interp), m_thread () { }
+
+    cli_input_reader (const cli_input_reader&) = delete;
+
+    cli_input_reader& operator = (const cli_input_reader&) = delete;
+
+    ~cli_input_reader (void)
+    {
+      // FIXME: Would it be better to ensure that
+      // interpreter::get_line_and_eval exits and then call
+      // m_thread.join () here?
+
+      m_thread.detach ();
+    }
+
+    void start (void)
+    {
+      m_thread = std::thread (&interpreter::get_line_and_eval, &m_interpreter);
+    }
+
+  private:
+
+    interpreter& m_interpreter;
+
+    std::thread m_thread;
+  };
+
   void interpreter::parse_and_execute (const std::string& input,
                                        bool& incomplete_parse)
   {
@@ -810,13 +949,34 @@
 
             if (options.server ())
               exit_status = server_loop ();
+            else if (options.experimental_terminal_widget ())
+              {
+                if (options.gui ())
+                  {
+                    m_event_manager.start_gui (true);
+
+                    exit_status = server_loop ();
+                  }
+                else
+                  {
+                    // Use an object so that the thread started for the
+                    // reader will be cleaned up no matter how we exit
+                    // this function.
+
+                    cli_input_reader reader (*this);
+
+                    reader.start ();
+
+                    exit_status = server_loop ();
+                  }
+              }
             else
               exit_status = main_loop ();
           }
       }
     catch (const exit_exception& xe)
       {
-        return xe.exit_status ();
+        exit_status = xe.exit_status ();
       }
 
     return exit_status;
@@ -1768,6 +1928,19 @@
       m_evaluator.dbcont ();
   }
 
+  // Provided for convenience.  Will be removed once we eliminate the
+  // old terminal widget.
+  bool interpreter::experimental_terminal_widget (void) const
+  {
+    if (! m_app_context)
+      return false;
+
+    // Embedded interpreters don't execute command line options.
+    const cmdline_options& options = m_app_context->options ();
+
+    return options.experimental_terminal_widget ();
+  }
+
   void interpreter::add_debug_watch_expression (const std::string& expr)
   {
     m_evaluator.add_debug_watch_expression (expr);
--- a/libinterp/corefcn/interpreter.h	Thu Mar 25 17:47:56 2021 -0400
+++ b/libinterp/corefcn/interpreter.h	Thu Mar 25 23:06:40 2021 -0400
@@ -74,6 +74,7 @@
 {
   class profiler;
   class child_list;
+  class push_parser;
 
   // The time we last time we changed directories.
   extern sys::time Vlast_chdir_time;
@@ -148,6 +149,11 @@
 
     void initialize (void);
 
+    // Note: GET_LINE_AND_EVAL is only used by new experimental terminal
+    // widget.
+
+    void get_line_and_eval (void);
+
     // Parse a line of input.  If input ends at a complete statement
     // boundary, execute the resulting parse tree.  Useful to handle
     // parsing user input when running in server mode.
@@ -507,6 +513,10 @@
     // Resume interpreter execution if paused.
     void resume (void);
 
+    // Provided for convenience.  Will be removed once we eliminate the
+    // old terminal widget.
+    bool experimental_terminal_widget (void) const;
+
     void handle_exception (const execution_exception& ee);
 
     void recover_from_exception (void);
--- a/libinterp/octave.cc	Thu Mar 25 17:47:56 2021 -0400
+++ b/libinterp/octave.cc	Thu Mar 25 23:06:40 2021 -0400
@@ -157,6 +157,10 @@
               m_exec_path = octave_optarg_wrapper ();
             break;
 
+          case EXPERIMENTAL_TERMINAL_WIDGET_OPTION:
+            m_experimental_terminal_widget = true;
+            break;
+
           case GUI_OPTION:
             m_gui = true;
             break;
@@ -324,6 +328,14 @@
     return instance ? instance->m_options.forced_interactive () : false;
   }
 
+  // Provided for convenience.  Will be removed once we eliminate the
+  // old terminal widget.
+  bool application::experimental_terminal_widget (void) const
+  {
+    return (instance
+            ? instance->m_options.experimental_terminal_widget () : false);
+  }
+
   application::~application (void)
   {
     // Delete interpreter if it still exists.
--- a/libinterp/octave.h	Thu Mar 25 17:47:56 2021 -0400
+++ b/libinterp/octave.h	Thu Mar 25 23:06:40 2021 -0400
@@ -57,6 +57,7 @@
     bool debug_jit (void) const { return m_debug_jit; }
     bool echo_commands (void) const { return m_echo_commands; }
 
+    bool experimental_terminal_widget (void) const { return m_experimental_terminal_widget; }
     bool forced_interactive (void) const { return m_forced_interactive; }
     bool forced_line_editing (void) const { return m_forced_line_editing; }
     bool gui (void) const { return m_gui; }
@@ -88,6 +89,7 @@
     void debug_jit (bool arg) { m_debug_jit = arg; }
     void echo_commands (bool arg) { m_echo_commands = arg; }
 
+    void experimental_terminal_widget (bool arg) { m_experimental_terminal_widget = arg; }
     void forced_line_editing (bool arg) { m_forced_line_editing = arg; }
     void forced_interactive (bool arg) { m_forced_interactive = arg; }
     void gui (bool arg) { m_gui = arg; }
@@ -128,6 +130,10 @@
     // (--echo-commands, -x)
     bool m_echo_commands = false;
 
+    // If TRUE, use new experimental terminal widget in the GUI.
+    // (--experimental-terminal-widget)
+    bool m_experimental_terminal_widget = false;
+
     // If TRUE, start the GUI.
     // (--gui) and (--force-gui) for backwards compatibility
     bool m_gui = false;
@@ -293,6 +299,10 @@
 
     void forced_interactive (bool arg) { m_options.forced_interactive (arg); }
 
+    // Provided for convenience.  Will be removed once we eliminate the
+    // old terminal widget.
+    bool experimental_terminal_widget (void) const;
+
     static application * app (void) { return instance; }
 
     static std::string program_invocation_name (void)
--- a/libinterp/options-usage.h	Thu Mar 25 17:47:56 2021 -0400
+++ b/libinterp/options-usage.h	Thu Mar 25 23:06:40 2021 -0400
@@ -36,7 +36,7 @@
 static const char *usage_string =
   "octave [-HVWdfhiqvx] [--debug] [--debug-jit] [--doc-cache-file file]\n\
        [--echo-commands] [--eval CODE] [--exec-path path]\n\
-       [--gui] [--help] [--image-path path]\n\
+       [--experimental-terminal-widget] [--gui] [--help] [--image-path path]\n\
        [--info-file file] [--info-program prog] [--interactive]\n\
        [--jit-compiler] [--line-editing] [--no-gui] [--no-history]\n\
        [--no-init-file] [--no-init-path] [--no-line-editing]\n\
@@ -78,6 +78,7 @@
 #define SERVER_OPTION 18
 #define TEXI_MACROS_FILE_OPTION 19
 #define TRADITIONAL_OPTION 20
+#define EXPERIMENTAL_TERMINAL_WIDGET_OPTION 21
 struct octave_getopt_options long_opts[] =
 {
   { "braindead",                octave_no_arg,       0, TRADITIONAL_OPTION },
@@ -88,6 +89,7 @@
   { "echo-commands",            octave_no_arg,       0, 'x' },
   { "eval",                     octave_required_arg, 0, EVAL_OPTION },
   { "exec-path",                octave_required_arg, 0, EXEC_PATH_OPTION },
+  { "experimental-terminal-widget", octave_no_arg,   0, EXPERIMENTAL_TERMINAL_WIDGET_OPTION },
   { "gui",                      octave_no_arg,       0, GUI_OPTION },
   { "help",                     octave_no_arg,       0, 'h' },
   { "image-path",               octave_required_arg, 0, IMAGE_PATH_OPTION },
@@ -140,6 +142,8 @@
   --echo-commands, -x     Echo commands as they are executed.\n\
   --eval CODE             Evaluate CODE.  Exit when done unless --persist.\n\
   --exec-path PATH        Set path for executing subprograms.\n\
+  --experimental-terminal-widget\n\
+                          Use new experimental terminal widget in the GUI.\n\
   --gui                   Start the graphical user interface.\n\
   --help, -h,             Print short help message and exit.\n\
   --image-path PATH       Add PATH to head of image search path.\n\
--- a/libinterp/parse-tree/pt-eval.cc	Thu Mar 25 17:47:56 2021 -0400
+++ b/libinterp/parse-tree/pt-eval.cc	Thu Mar 25 23:06:40 2021 -0400
@@ -777,6 +777,8 @@
 
     do
       {
+        m_parser->reset ();
+
         try
           {
             // FIXME: Should we call octave_quit in the octave::sleep
@@ -798,8 +800,6 @@
             octave_interrupt_state = 1;
             m_interpreter.recover_from_exception ();
 
-            m_parser->reset ();
-
             // Required newline when the user does Ctrl+C at the prompt.
             if (m_interpreter.interactive ())
               octave_stdout << "\n";
@@ -808,8 +808,6 @@
           {
             m_interpreter.recover_from_exception ();
 
-            m_parser->reset ();
-
             std::cerr << "error: unhandled index exception: "
                       << e.message () << " -- trying to return to prompt"
                       << std::endl;
@@ -824,8 +822,6 @@
             if (m_interpreter.interactive ())
               {
                 m_interpreter.recover_from_exception ();
-
-                m_parser->reset ();
               }
             else
               {
@@ -838,15 +834,16 @@
           {
             octave_interrupt_state = 1;
             m_interpreter.recover_from_exception ();
-
-            m_parser->reset ();
+          }
+        catch (const exit_exception& xe)
+          {
+            m_exit_status = xe.exit_status ();
+            break;
           }
         catch (const std::bad_alloc&)
           {
             m_interpreter.recover_from_exception ();
 
-            m_parser->reset ();
-
             std::cerr << "error: out of memory -- trying to return to prompt"
                       << std::endl;
           }
--- a/src/main.in.cc	Thu Mar 25 17:47:56 2021 -0400
+++ b/src/main.in.cc	Thu Mar 25 23:06:40 2021 -0400
@@ -76,8 +76,13 @@
 #include "shared-fcns.h"
 
 #if defined (HAVE_OCTAVE_QT_GUI) && ! defined (OCTAVE_USE_WINDOWS_API)
+static bool fork_and_exec = false;
+#else
+static bool fork_and_exec = true;
+#endif
 
-// Forward signals to the GUI process.
+// If we fork and exec, we'll need the following signal handling code to
+// forward signals to the GUI process.
 
 static pid_t gui_pid = 0;
 
@@ -155,8 +160,6 @@
   gui_driver_set_signal_handler ("SIGXFSZ", gui_driver_sig_handler);
 }
 
-#endif
-
 static std::string
 get_octave_bindir (void)
 {
@@ -219,6 +222,7 @@
 
   bool eval_code = false;
   bool persist_octave = false;
+  bool experimental_terminal_widget = false;
 
   set_octave_home ();
 
@@ -282,6 +286,13 @@
           start_gui = true;
           idx_gui = i;
         }
+      else if (! strcmp (argv[i], "--experimental-terminal-widget"))
+        {
+          // If we see this option, then we don't fork and exec.
+
+          fork_and_exec = false;
+          new_argv[k++] = argv[i];
+        }
       else if (! strcmp (argv[i], "--persist"))
         {
           // FIXME: How can we reliably detect if this option appears
@@ -428,9 +439,7 @@
   octave_block_async_signals ();
   octave_block_signal_by_name ("SIGTSTP");
 
-#if defined (HAVE_OCTAVE_QT_GUI) && ! defined (OCTAVE_USE_WINDOWS_API)
-
-  if (gui_libs && start_gui)
+  if (fork_and_exec && gui_libs && start_gui)
     {
       // Fork and exec when starting the GUI so that we will call
       // setsid to give up the controlling terminal (if any) and so that
@@ -508,14 +517,6 @@
         std::cerr << argv[0] << ": " << std::strerror (errno) << std::endl;
     }
 
-#else
-
-  retval = octave_exec (file, new_argv);
-
-  if (retval < 0)
-    std::cerr << argv[0] << ": " << std::strerror (errno) << std::endl;
-
-#endif
 
   return retval;
 }