jupyter-xeus / cpp-terminal

C++ library for writing multiplatform terminal applications
https://jupyter-xeus.github.io/cpp-terminal/
Other
494 stars 53 forks source link

Fixes and new features #239

Closed flagarde closed 1 year ago

flagarde commented 1 year ago

Sorry for the big number of commits but I have to move from Linux to Windows and these changes are quite profound.

Mainly : 1) Fix #32 : But it uses https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter. This requires constructor without parameters. So a big refactoring was needed but I think it makes the class more logical (to me :)). It would be very nice for people who just want to activate ANSII Escape code without relying on the all library. I can't imagine a more simplier .hpp :)

#pragma once

namespace Term
{

class Terminal;
extern Terminal& terminal;

class Initializer
{
public:
  Initializer();
  ~Initializer();

private:
  static int m_counter;
};

static Initializer m_Initializer;

}  // namespace Term

NOT more.

including the line mentioned 'io.hpp' (I know the name is ugly) and linking to cpp-terminal allows to have a minimal terminal setup. It turn ON ANSII escape code in windows etc without adding nothing in the code (see examples/minimal.cpp) and restore the old state when quiting. This lightweight version could interest many people I think.

2) Allow to use Term::terminal<< or Term::terminal>> adding "terminal.hpp" (see examples/cin.cpp) This operator always read, write to console (even if stdin stdout as been redirected)

3) By default the terminal is no Cooked you need to explicitly turn it into RawMode (see examples/cin.cpp etc...). It was necessary to allow 1)

Cleaning, warning fixes etc...

An other PR will follow with futher cleaning (2nd Nifty_Counter to avoid open/close file handlers and simplify code).

MCWertGaming commented 1 year ago

Hey @flagarde wouldn't it make more sense to use a std::shared_ptr? It basically does the same "nifty counter" stuff but provides us with a complete solution and some basic management. Or what's the point here?

flagarde commented 1 year ago

@MCWertGaming std::shared_ptr assure you the destruction when you don't use the ressource anymore but look in examples/minimal.cpp, you never explicitly use the ressource so it would be created and destroyed.

Nifty gives you: 1) The ressource is created before entering in main. 2) Only one instance 3) Destroyed only when the program stops.

This is not possibe to my knowledge with a raw share_ptr.

An other drawback would be for the user . Look how simple is the syntax for a c++ user

std::cout << "This should be printed in terminal if stdout is not redirected" << std::endl;
Term::terminal << "Now type a number and then a string !" << std::endl;

with shared_ptr the syntax would be different. Maybe we could add some typedef but i'm not sure it is usefull. The magic trick is hidden to the user.

An other feature PR which will follow is that in many places you can see

 HANDLE hConOut{CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)};
  HANDLE hConIn{CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)};

to explicitly talk to the terminal and not the stdin stdout etc that could be redirected by the user. This can be avoid if we use a second nifty counter to open this handle and close them at the end.. This is important because the OS has some limit on the number of open files.. If they are not well closed it could create very hard to debug error. Assuring the opening once at start we are sure the functions will work and that they will be closed at the end of the program

One of the advantage of this is the simplicity for the user to have ANSII escape code etc on windows. {fmt} users for exemple would benefits from it. Just adding the io.hpp everywhere they use io function would automatically provide the feature without to take care if some Terminal instance has been created or not

MCWertGaming commented 1 year ago

Yeah, pretty nice, thank you!

iganinja commented 1 year ago

Hi! Does anyone tested the latest version on Windows? I got a working program and with latest update I got everything totally broken with exactly the very same code (except adapting Terminal constructor so now I call setOptions)

From this: image

To this: image

flagarde commented 1 year ago

Hi, I tested it on windows but maybe not enought.. could you share your code ? The way the library is functioning has changed drastically recently.

Could you tell me what terminal you are using too? It seems the code think your terminal doesn't support escape codes...

Does the examples provided with cpp-terminal are working fine ? colors for example

flagarde commented 1 year ago

Using QT creator. the menu example is working fine...

Are you using something like :

#include "cpp-terminal/terminal.hpp"
...
Term::terminal.setOptions({Term::Option::ClearScreen, Term::Option::NoSignalKeys,Term::Option::Raw});
...

Screenshot_20230428_225348

iganinja commented 1 year ago

Hi

Color works indeed, also menu window example, but if I keep the key pressed the menu box goes up and down one row. I guess it's because of terminal scrolling not exact adjustment. Also if I maximize the terminal window, it draws 2 menus.

I was not being able to make my program to work at the moment, I'll try again later.

By the way, why are the method parameters missing in .h files? When using autocompletion you only get things like (size_t, size_t, ...) instead of (size_t x, size_t y, ...), which is very annoying.

Thanks.

flagarde commented 1 year ago

@iganinja Are you using this kind of code to be aware of the change of terminal size. For now I think you need to clear and redraw by hand the screen :

      render(term_size.rows(), term_size.columns(), h, w, pos);
      Term::Event event = Term::read_event();
      switch(event.type())
      {
        case Term::Event::Type::Key:
          switch(Term::Key(event))
          {
            case Term::Key::ARROW_LEFT:
              if(w > 10) w--;
              break;
            case Term::Key::ARROW_RIGHT:
              if(w < (term_size.columns() - 5)) w++;
              break;
            case Term::Key::ARROW_UP:
              if(pos > 1) pos--;
              break;
            case Term::Key::ARROW_DOWN:
              if(pos < h) pos++;
              break;
            case Term::Key::HOME: pos = 1; break;
            case Term::Key::END: pos = h; break;
            case Term::Key::q:
            case Term::Key::ESC:
            case Term::Key::CTRL_C: on = false; break;
            default: break;
          }
          break;
        case Term::Event::Type::Screen:
          term_size = Term::Screen(event);
          std::cout << Term::clear_screen() << std::flush;
          render(term_size.rows(), term_size.columns(), h, w, pos);
          break;
        default: break;
      }
    }
        case Term::Event::Type::Screen:
          term_size = Term::Screen(event);
          std::cout << Term::clear_screen() << std::flush;
          render(term_size.rows(), term_size.columns(), h, w, pos);
          break;

is where the magic happens. If you can/ are allowed to send your code I could have a look. If it is not so big and if you agree it would be nice to have it as example or test has it seems to be able to detect a lot of regression on the library :)

the method parameters are missing on .h just by my faulty preference (mainly because im very bad on giving names so I avoid them in .h as this is accessible to the end user). But I have proposed a PR to have a doxygen documentation #245 so this defect should be solved gradualy

iganinja commented 1 year ago

Hi again @flagarde

I just created a new public repository on my github account, so you can get the code easily: https://github.com/iganinja/tmse I pushed the latest version, which compiles but shows the incorrect "graphics" on the terminal.

I would be really glad to help cpp-terminal to mature with TMSE. There is some code already but I think you could follow with no problem. Take a look at the Widget class. I control the terminal resizing in MainWindow class. Don't hesitate to ask me anything about it.

Respect to doxygen documentation, I would be happy just having the same parameter names we have in the .cpp files. With that it would be enough, as autocomplete would work properly.

Thanks!

flagarde commented 1 year ago

@iganinja I did basic checks on Linux and one thing very strange is that the termina is not restored in CookedMode when the program exit :(

If I understand correctly you have problem with resizing ? I did this :

#include "mainwindow.h"
#include "tasks/mainwindowtasks.h"
#include "tasks/mainmenutasks.h"

#include <cpp-terminal/terminal.hpp>
#include "cpp-terminal/screen.hpp"
#include <thread>
#include <iostream>

using namespace Olagarro::Tasks;
using namespace TMSETasks;
using namespace std::chrono_literals;

MainWindow::MainWindow() :
    testingSelectionWindow{"About", "Text Mode Sane Editor v0.1", {"OK"}, 40, 1, 1}
{

    Term::terminal.setOptions({Term::Option::Raw});
    Term::terminal_title("Text Mode Sane Editor");
    checkTerminalResizing();

    std::string text
    {
        "Esto es una prueba de un texto que tiene que partirse pero al nivel de las palabras, no a nivel de carácteres."
    };

    testingTextBox.setTitle("Warning");
    testingTextBox.setText(std::move(text));

    toggleMainMenuVisibility();
}

void MainWindow::run()
{
    createTasks();

    draw();

    auto shouldRedraw{true};

    while(mShouldKeepRunning)
    {
        mCurrentKey = Term::Key::NO_KEY;

      Term::Event event = Term::read_event();
      switch(event.type())
      {
        case Term::Event::Type::Key:
          mCurrentKey = Term::Key{event};
          shouldRedraw = true;
          break;
        case Term::Event::Type::Screen:
        {
          shouldRedraw = true;
          Term::Screen screen(event);
          mTerminalWindow = std::make_unique<Term::Window>(screen.columns(), screen.rows());
          onResize(screen.columns(), screen.rows());
          break;
        }
        default: break;
      }

        if(shouldRedraw)
        {

            draw();
        }
mTaskExecutor.update();
       // std::this_thread::sleep_for(1ms);

        //checkTerminalResizing();
    }
}

Term::Key MainWindow::currentKey() const
{
    return mCurrentKey;
}

void MainWindow::consumeCurrentKey()
{
    mCurrentKey = Term::Key::NO_KEY;
}

void MainWindow::exit()
{
    mShouldKeepRunning = false;
}

Widgets::MainMenu& MainWindow::mainMenu()
{
    return mMainMenu;
}

void MainWindow::toggleMainMenuVisibility()
{
    mMainMenu.setVisible(!mMainMenu.isVisible());
    onResize(mLastTerminalSize.x, mLastTerminalSize.y);
    draw();
}

void MainWindow::toggleWelcomeBackgroundVisibility()
{
    onResize(mLastTerminalSize.x, mLastTerminalSize.y);
    draw();
}

void MainWindow::draw()
{
    mTerminalWindow->clear();

    // WARNING Temporal
    if(mSize.x <= 40 || mSize.y < 10)
    {
        return;
    }

    mFilesTabs.draw(*mTerminalWindow);
    mMainMenu.draw(*mTerminalWindow);
    testingSelectionWindow.draw(*mTerminalWindow);
    testingTextBox.draw(*mTerminalWindow);

    std::cout << mTerminalWindow->render(1, 1, true) << std::flush;
}

void MainWindow::onResize(size_t newWidth, size_t newHeight)
{
    mSize = Utils::Size{newWidth, newHeight};

    if(mMainMenu.isVisible())
    {
        mMainMenu.setPosition(0, 0);
        mMainMenu.onResize(newWidth, 1);

        mFilesTabs.setPosition(0, 1);
        mFilesTabs.onResize(newWidth, newHeight - 1);

        testingSelectionWindow.setPosition(1, 2);
    }
    else
    {
        mFilesTabs.setPosition(0, 0);
        mFilesTabs.onResize(newWidth, newHeight);

        testingSelectionWindow.setPosition(1, 1);
    }

    testingTextBox.setPosition(10, 20);
    testingTextBox.onResize(newWidth * 0.8, 6);
}

void MainWindow::checkTerminalResizing()
{
    auto currentTerminalSize{Term::screen_size()};
    const Utils::Size currentSize{currentTerminalSize.columns(), currentTerminalSize.rows()};

    if(mLastTerminalSize != currentSize)
    {
        mTerminalWindow = std::make_unique<Term::Window>(currentSize.x, currentSize.y);
        onResize(currentSize.x, currentSize.y);
        mLastTerminalSize = currentSize;
    }
}

void MainWindow::createTasks()
{
    mTaskExecutor.addTask(TMSETasks::mainWindowTasks(*this));
    mTaskExecutor.addTask(TMSETasks::mainMenu(*this));
}

It works but sometimes is crashing and when I press F4 the size is not propagated somewhere. Now cpp-terminal has its own way to detect terminal size change. This requires more changes in your code but it should be more efficient as it uses platform specifics way to detect this changes. (signal on Linux and WINDOW_BUFFER_SIZE_EVENT and Windows).

For me the most strange thing is that the terminal is not turned back in Cooked Mode when I quit the program. I need to investigate this too

iganinja commented 1 year ago

Hi @flagarde .

Sorry for the late answer, I've been out of my home this week and I had not time to test your proposal.

I copied and pasted your MainWindow.cpp file on my Windows code and still no luck, I only get the garbage I posted previously in the second screenshot.

What I did also is to download and build TMSE's code from github, in Linux, and it seems to work. At least it draws everything and it reacts correctly to input.

I don't understand how menu demo works but my code doesn't, as far as I know there are doing the same, drawing things in Window object in the same way and then showing it in the terminal screen with std::cout + std::flush.

flagarde commented 1 year ago

@iganinja Hi, Yes I agree it's very strnage situation. Cousl you give more information about your system? Windows version etc to try to dig into this very strange problem. I will try on my virtualbox. From the picture it seems to have 2 problems :

Console is not turned into ASCII escape mode and some garbage texts.

I imagine you use the terminal provided by QTcreator right ? Have you try to use the windows standard one ? Maybe QT creator add a layer on top of the terminal and this is maybe not detected my the library. I suggest to create a dedicated issue for this :). It would be easier for people to see this issue and maybe someone have the solution. I'm not expert on Windows "logics"

iganinja commented 1 year ago

I launched a cmd terminal and typed ver, I got this:

Microsoft Windows [Versión 10.0.19044.2846]

Then, I run the executable and got the same garbage I get running it in Qt Creator's terminal, so unfortunately it's not something related to this IDE. Previous versions of cpp-terminal worked inside Qt Creator's terminal, so I think there is no intermediate layer.

flagarde commented 1 year ago

@iganinja I agree, Let me do some basic tests. Which compiler are you using ? which version?

It's very strange color is working on main.cpp

 try
    {
        if(!Term::is_stdin_a_tty())
        {
            std::cout << "The terminal is not attached to a TTY and therefore can't catch user input. Exiting...\n";
            return 1;
        }
        std::cout<<Term::color_fg(Term::Color::Name::Blue)<<"Test"<<Term::color_fg(Term::Color::Name::Default)<<std::endl;
        loadSettings();

        MainWindow mainWindow;

        mainWindow.run();
    }

Test is printed in blue. Something very strange. Do you think you could give a simpler version of your software ?

I don't know if it's a cpp-terminal library problem ?

iganinja commented 1 year ago

Hi again @flagarde

I tested your last code and it works, I get the blue foreground and black background:

image

I'm using MinGW 11.2.0 64 bits. Anyway, I'm using VC++ compiler in my gitlab repository in a job, and it creates an executable with the same behavior (I mean, with my full app, not the test you just posted).

Is it possible that something got messed up in Window class? As far as I understand, Window just takes the buffer created with the different calls and then translates everything to calls like the test you posted right now. Previous version of the library was working like a charm, as you can see in the screenshots.

flagarde commented 1 year ago

I have found the problem. Now the library create a terminal automatically. You don't need to create any on the classes you use.. Please suppress Term::Terminal mTerminal; on mainwindows.h. I have this : Screenshot_20230508_010445

Maybe I should add a crashing if people create other instance of the class or be more precise on the new way cpp-terminal is working

Please see : https://github.com/jupyter-xeus/cpp-terminal/blob/master/cpp-terminal/io.hpp

iganinja commented 1 year ago

I have found the problem. Now the library create a terminal automatically. You don't need to create any on the classes you use.. Please suppress Term::Terminal mTerminal; on mainwindows.h. I have this : Screenshot_20230508_010445

Maybe I should add a crashing if people create other instance of the class or be more precise on the new way cpp-terminal is working

Please see : https://github.com/jupyter-xeus/cpp-terminal/blob/master/cpp-terminal/io.hpp

What a coincidence, I just found right now that this also breaks the test:

Term::terminal.setOptions({Term::Option::ClearScreen, Term::Option::NoSignalKeys, Term::Option::NoCursor, Term::Option::Raw});`
std::cout<<Term::color_fg(Term::Color::Name::BrightBlue)<<"Test"<<Term::color_fg(Term::Color::Name::Default)<<std::endl;

This is without creating a Terminal object. It does not show anything on the terminal, while not calling setOptions() allows "Test" to be shown like in the previous post.

image

flagarde commented 1 year ago

@iganinja Which kind of problem ?

#include <cpp-terminal/exception.hpp>
#include <cpp-terminal/input.hpp>
#include <cpp-terminal/terminal.hpp>
#include <cpp-terminal/tty.hpp>
#include <cpp-terminal/window.hpp>
#include <cpp-terminal/options.hpp>

#include "settings.h"
#include "mainwindow.h"
#include "utils/exception.h"
#include "cpp-terminal/color.hpp"

#include <iostream>
#include <thread>
#include <chrono>

using namespace std::chrono_literals;

int main()
{
    try
    {

        if(!Term::is_stdin_a_tty())
        {
            std::cout << "The terminal is not attached to a TTY and therefore can't catch user input. Exiting...\n";
            return 1;
        }
        std::cout<<Term::color_fg(Term::Color::Name::Blue)<<"Test"<<Term::color_fg(Term::Color::Name::Default)<<std::endl;
        loadSettings();
        int i;
        std::cin>>i;
        //MainWindow mainWindow;

        //mainWindow.run();
    }
    catch(Utils::Exception& exception)
    {
        std::cout << "Error: " << exception.message() << "\n";
        return 1;
    }
    catch(const Term::Exception& exception)
    {
        std::cout << "cpp-terminal error: " << exception.what() << "\n";
        return 2;
    }
    catch(const std::exception& exception)
    {
        std::cout << "Unknown error: " << exception.what() << "\n";
        return 3;
    }

    return 0;
}

Is still working for me

iganinja commented 1 year ago

I tried without Terminal object on MainWindow and now it works. Thank you very much @flagarde!

image

flagarde commented 1 year ago

@iganinja You are welcome :) . To be sure the terminal is created automatically, you can add io.hpp. If you need to change options just add terminal.hpp call setoptions and that's it (terminal.hpp called io.hpp). The API is still not fixed maybe the class terminal should be hidden to users to avoid creating one. The changes have been made very recently and it's a big change on the way cpp-terminal is working but IMHO it makes its use easier especially on windows.

Terminal is created the same way std::cout is created so think of Term:terminal as std::cout. You can even do Term::terminal<<"Test" if you want to be sure to print to the terminal and not cout. Very useful if cout has been redirected :)

certik commented 1 year ago

@flagarde thank you for the PR and all the Windows fixes!

Thanks @MCWertGaming for reviewing and merging.

@iganinja I want to create menus like you have. Do we have this functionality in cpp-terminal, or not yet? If not, then let's create it, it would be useful to many.

flagarde commented 1 year ago

@certik Thx for your nice words. We don't have this yet but I'm still focused on polishing the basic features and cleaning the API. I hope the library is becoming easier to use and have much features. UTF8 is still a bit hard to make correct on this library and it would be a nice addition I think. Mouse event would be nice to have too :). In windows it's easy but on linux it depends on the terminal and need escape code parsing.

After the feature you requires could be built on top. Of course this menu could be coded with the actual library and updated while the basics are changing.

certik commented 1 year ago

@flagarde awesome! Great plan.

What doesn't work with UTF-8? I thought I got the basics working.

iganinja commented 1 year ago

About UTF8, would it be possible to use https://github.com/nemtrif/utfcpp? I'm using it on my program, it seems mature and reliable.

@certik As @flagarde said, there is no menu or widget concept in cpp-terminal. I think it would be nice to have an optional layer on top of cpp-terminal in order to build more sophisticated apps it if the user wants to. Do you want only menus, or a full set of useful widgets?

Related to that, the other day I found this: https://github.com/gansm/finalcut In short, it's a terminal mode Qt library, in the sense that it works like Qt in regards to widgets but in text mode. Unfortunately it only works in Linux and other Posix systems, but not natively in Windows.

flagarde commented 1 year ago

@certik You are right the basics are here :). But I discovered some corner cases for example, chinese user could type in pinyin and had a program to print 中 on the terminal, this hanzi in the code was splited in byte and read one byte by byte now it is read as full utf8 but no detecting on if it's utf8 character or a bounch of byte copy pasted is done yet. For this kind of language it could be nice to have this feature, input not detected as key but as codepoint and act accordingly. Windows utf8 detectin etc seems to be checked size of character (printed) is necessary too (chinese or CJK characters for example are mainly 2 times bigger than (2 spaces) ASCII characters or european ones) ...

flagarde commented 1 year ago

@certik @iganinja There is indeed good alternatives.. I searched for long time the best alternative (for me...), clean easy to understand not too much dependency, cross-platform and found this library.. Looking into the code I liked the style and I saw it could be easily extended. Most alternatives are not cross-platform, C style coding or depend on big libraries... cpp-terminal offer some nice features others seem not to provide. It could be extended of course (I have still some ideas in mind if the maintainers accept the PR of course). Unfortunately I'm adding such idea a bit randomly or by step as some feature will/ necesitate/d big changes on the API and codes. I agree with you @iganinja , adding layers is a good choice (onion based library :) ). I really enjoy C++ for its "Pay only for what you use" philosophy and IMHO lirary should try to follow this rule.

certik commented 1 year ago

I am glad you like it @flagarde (yes, let's fix the utf-8 issues that you found). I did a thorough research of many libraries out there some time ago, but it was removed in https://github.com/jupyter-xeus/cpp-terminal/pull/147. @MCWertGaming why not having a document where we list these alternatives? They are all good libraries to be inspired from, that's why I put them into the README. Maybe we can put them into a separate file. Let's add https://github.com/gansm/finalcut as @iganinja suggested above and then let's also add https://github.com/Textualize/textual, which has impressive widgets. Also just found https://github.com/charmbracelet/bubbletea and https://github.com/vadimdemedes/ink, both have links in the readme for projects that use them.

I think it would be nice to have an optional layer on top of cpp-terminal in order to build more sophisticated apps it if the user wants to. Do you want only menus, or a full set of useful widgets?

I would do full widgets, either Turbo Vision style, or probably using the modern "react" like approach as in textual. There is also imgui style. I am not sure which one is the best approach, but we can start with one, and we can iterate. I believe Textual allows you to run the same code in a terminal (nice TUI) as well as in the browser. We should try to do the same with this optional "widget" layer, then we can optionally compile the C++ code to WASM and it would run in a browser with a nice GUI interface.

iganinja commented 1 year ago

I am glad you like it @flagarde (yes, let's fix the utf-8 issues that you found). I did a thorough research of many libraries out there some time ago, but it was removed in #147. @MCWertGaming why not having a document where we list these alternatives? They are all good libraries to be inspired from, that's why I put them into the README. Maybe we can put them into a separate file. Let's add https://github.com/gansm/finalcut as @iganinja suggested above and then let's also add https://github.com/Textualize/textual, which has impressive widgets. Also just found https://github.com/charmbracelet/bubbletea and https://github.com/vadimdemedes/ink, both have links in the readme for projects that use them.

I think it would be nice to have an optional layer on top of cpp-terminal in order to build more sophisticated apps it if the user wants to. Do you want only menus, or a full set of useful widgets?

I would do full widgets, either Turbo Vision style, or probably using the modern "react" like approach as in textual. There is also imgui style. I am not sure which one is the best approach, but we can start with one, and we can iterate. I believe Textual allows you to run the same code in a terminal (nice TUI) as well as in the browser. We should try to do the same with this optional "widget" layer, then we can optionally compile the C++ code to WASM and it would run in a browser with a nice GUI interface.

I like Turbo Vision visual style, imitating a graphical GUI. From the programming perspective, having a Widget base class which can contain children and following a Qt like approach is the most familiar one to me. I did some half-baked approach on my program so far, but it would need to be more sophisticated. Should we create a new thread to talk about how to engage this development?

I also like the WASM idea, it would great to be able to create text mode like web apps.

flagarde commented 1 year ago

@iganinja @certik When I created the CI I added the dockcross ones which comes with wasm but my knowledge is limited on this but the code seems to compile (https://github.com/jupyter-xeus/cpp-terminal/actions/runs/4780025414/jobs/8497486942) but I don't know how this works and if the code is running fine. Maybe some of you could have test or if you have a link to know what wasm is about :). I think it is possible to activate in the repo Discussions and some nice features like this could be discussed (technically it is not issues but it would make the library "sweeter")

flagarde commented 1 year ago

@iganinja @certik utf8 is really something the library should deal with (in Windows it's quite messy but they improve from time to time). We can try to stay away of external library as much as possible as we don't need all the feature provided by most of the utf8 library. but maybe we will have to make the jump but I think it worth trying to try without any dependency first.

certik commented 1 year ago

Re utf-8: yes, let's try without a dependency and see. If it's too much for us to handle and maintain, we can pull in a dependency.

I started a discussion here:

https://github.com/jupyter-xeus/cpp-terminal/discussions/247