Small header only C++ library for writing multiplatform terminal applications

Related tags

CLI cpp-terminal
Overview

Terminal

Terminal is small header only library for writing terminal applications. It works on Linux, macOS and Windows (in the native cmd.exe console). It supports colors, keyboard input and has all the basic features to write any terminal application.

It has a small core (terminal_base.h) that has a few platform specific building blocks, and a platform independent library written on top using the ANSI escape sequences (terminal.h).

This design has the advantage of having only a few lines to maintain on each platform, and the rest is platform independent. We intentionally limit ourselves to a subset of features that all work on all platforms natively. That way, any application written using Terminal will work everywhere out of the box, without emulation. At the same time, because the code of Terminal is short, one can easily debug it if something does not work, and have a full understanding how things work underneath.

Examples

Several examples are provided to show how to use Terminal. Every example works natively on all platforms:

  • kilo.cpp: the kilo text editor ported to C++ and Terminal instead of using Linux specific API.
  • menu.cpp: Shows a menu on the screen
  • keys.cpp: Listens for keys, showing their numbers
  • colors.cpp: Shows how to print text in color to standard output

How to use

The easiest is to just copy the two files terminal.h and terminal_base.h into your project. Consult the examples how to use it. You can just use the terminal_base.h, which is a standalone header file, if you only want the low level platform dependent functionality. Use terminal.h, which depends on terminal_base.h, if you also want the platform independent code to more easily print escape sequences and/or read and translate key codes.

Documentation

We will start from the simplest concept (just printing a text on the screen) and then we will keep adding more features such as colors, cursor movement, keyboard input, etc., and we will be explaining how things work as we go.

Printing

To print text into standard output, one can use std::cout in C++:

std::cout << "Some text" << std::endl;

One does not need Terminal for that.

Colors

To print colors and other styles (such as bold), use the Term::color() function and Term::fg enum for foreground, Term::bg enum for background and Term::style enum for different styles (see the colors.cpp example):

#include <cpp-terminal/terminal.h>
using Term::color;
using Term::fg;
using Term::bg;
using Term::style;
int main() {
    try {
        Term::Terminal term;
        std::string text = "Some text with "
            + color(fg::red) + color(bg::green) + "red on green"
            + color(bg::reset) + color(fg::reset) + " and some "
            + color(style::bold) + "bold text" + color(style::reset) + ".";
        std::cout << text << std::endl;
    } catch(...) {
        throw;
    }
    return 0;
}

One must call Term::fg::reset, Term::bg::reset and Term::style::reset to reset the given color or style.

One must create the Term::Terminal instance. In this case, the Terminal does nothing on Linux and macOS, but on Windows it checks if the program is running withing the Windows console and if so, enables ANSI escape codes in the console, which makes the console show colors properly. One must have a try/catch block in the main program to ensure the Terminal's destructor gets called (even if an unhandled exception occurs), which will put the console into the original mode.

The program might decide to print colors not only if it is in a terminal (which can be checked by term.is_stdout_a_tty()), but also when not run in a terminal, some examples:

  • Running on a CI, e.g. AppVeyor, Travis-CI and Azure Pipelines all show colors properly
  • Using less -r shows colors properly (but less does not)
  • Printing colors in program output in a Jupyter notebook (and then possibly converting such colors from ANSI sequences to html)

An example when the program might not print colors is when the standard output gets redirected to a file (say, compiler error messages using g++ a.cpp > log), and then the file is read directly in some editor.

The color() function always returns a string with the proper ANSI sequence. The program might wrap this in a macro, that will check some program variable if it should print colors and only call color() if colors should be printed.

Cursor movement and its visibility

The next step up is to allow cursor movement and other ANSI sequences. For example, here is how to render a simple menu (see menu.cpp example) and print it on the screen:

void render(int rows, int cols, int pos)
{
    std::string scr;
    scr.reserve(16*1024);

    scr.append(cursor_off());
    scr.append(move_cursor(1, 1));

    for (int i=1; i <= rows; i++) {
        if (i == pos) {
            scr.append(color(fg::red));
            scr.append(color(bg::gray));
            scr.append(color(style::bold));
        } else {
            scr.append(color(fg::blue));
            scr.append(color(bg::green));
        }
        scr.append(std::to_string(i) + ": item");
        scr.append(color(bg::reset));
        scr.append(color(fg::reset));
        scr.append(color(style::reset));
        if (i < rows) scr.append("\n");
    }

    scr.append(move_cursor(rows / 2, cols / 2));

    scr.append(cursor_on());

    std::cout << scr << std::flush;
}

This will accumulate the following operations into a string:

  • Turn off the cursor (so that the terminal does not show the cursor quickly moving around the screen)
  • Move the cursor to the (1,1) position
  • Print the menu in color and highlighting the selected item (specified by pos)
  • Move the cursor to the middle of the screen
  • Turn on the cursor

and print the string. The std::flush ensures that the whole string ends up on the screen.

Saving the original screen and restoring it

It is a good habit to restore the original terminal screen (and cursor position) if we are going move the cursor around and draw (as in the previous section). To do that, call the save_screen() method:

Term::Terminal term;
term.save_screen();

This issues the proper ANSI sequences to the terminal to save the screen. The Terminal's destructor will then automatically issue the corresponding sequences to restore the original screen and the cursor position.

Keyboard input

The final step is to enable keyboard input. To do that, one must set the terminal in a so called "raw" mode:

Terminal term(true);

On Linux and macOS, this disables terminal input buffering, thus every key press is immediately sent to the application (otherwise one has to press ENTER before any input is sent). On Windows, this turns on ANSI keyboard sequences for key presses.

The Terminal's destructor then properly restores the terminal to the original mode on all platforms.

One can then wait and read individual keys and do something based on that, such as (see menu.cpp):

int key = term.read_key();
switch (key) {
    case Key::ARROW_UP: if (pos > 1) pos--; break;
    case Key::ARROW_DOWN: if (pos < rows) pos++; break;
    case 'q':
    case Key::ESC:
          on = false; break;
}

Now we have all the features that are needed to write any terminal application. See kilo.cpp for an example of a simple full screen editor.

Similar Projects

Colors

Libraries to handle color output.

C++:

Drawing

JavaScript:

Prompt

Libraries to handle a prompt in terminals.

C and C++:

Python:

General TUI libraries

C and C++:

Python:

Go:

Rust:

JavaScript:

Comments
  • Recommended version

    Recommended version

    Hello there, I would like to use cpp-terminal in Mamba. Specifically we'd like to use it for a yes/no prompt with Ctrl+C support. What's the recommended version to use right now? Would you recommend using latest master, or v0.1, or would you consider publishing a more recent stable release?

    enhancement question 
    opened by jonashaag 27
  • Merge of LFortran cpp-terminal changes

    Merge of LFortran cpp-terminal changes

    Manual merge of current cpp-terminal on LFortran and the main repo version. I confirmed this compiles in LFortran and things generally seem to work but the REPL test fails (runtime_error: tcgetattr() failed is thrown). Perhaps some testing of the examples here will reveal the problem.

    opened by dpoerio 22
  • Cutting into multiple headers

    Cutting into multiple headers

    This pull-request resolves #67 and cuts terminal.h and base_terminal.h into multiple smaller headers.

    Things that need to be done:

    • [x] cut headers
    • [x] move source code into source files
    • [x] fix windows includes
    • [x] remove not required includes from files for windows
    • [x] test macos and windows
    opened by MCWertGaming 20
  • Is there a minimum version of GNU this should build with on Linux?

    Is there a minimum version of GNU this should build with on Linux?

    Error I am getting after issuing cmake.

    [email protected]:~/dev/cpp-terminal2/bld$ cmake .. -- The CXX compiler identification is GNU 6.3.0 -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Check for working CXX compiler: /usr/bin/c++ - skipped -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: /home/cbunders/dev/cpp-terminal2/bld [email protected]:~/dev/cpp-terminal2/bld$ ls CMakeCache.txt Makefile cmake_install.cmake examples CMakeFiles _deps cpp-terminal tests CTestTestfile.cmake cmake cpp-terminalConfigVersion.cmake [email protected]:~/dev/cpp-terminal2/bld$ make [ 3%] Building CXX object cpp-terminal/CMakeFiles/cpp-terminal.dir/base.cpp.o In file included from /usr/include/c++/6/memory:81:0, from /home/cbunders/dev/cpp-terminal2/cpp-terminal/private/platform.hpp:25, from /home/cbunders/dev/cpp-terminal2/cpp-terminal/base.hpp:4, from /home/cbunders/dev/cpp-terminal2/cpp-terminal/base.cpp:1: /usr/include/c++/6/bits/unique_ptr.h: In instantiation of 'void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = termios]': /usr/include/c++/6/bits/unique_ptr.h:239:17: required from 'std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = termios; _Dp = std::default_delete]' /usr/include/c++/6/bits/unique_ptr.h:204:61: required from 'constexpr std::unique_ptr<_Tp, _Dp>::unique_ptr(std::nullptr_t) [with _Tp = termios; _Dp = std::default_delete; std::nullptr_t = std::nullptr_t]' /home/cbunders/dev/cpp-terminal2/cpp-terminal/private/platform.hpp:65:50: required from here /usr/include/c++/6/bits/unique_ptr.h:74:22: error: invalid application of 'sizeof' to incomplete type 'termios' static_assert(sizeof(_Tp)>0, ^ cpp-terminal/CMakeFiles/cpp-terminal.dir/build.make:75: recipe for target 'cpp-terminal/CMakeFiles/cpp-terminal.dir/base.cpp.o' failed make[2]: *** [cpp-terminal/CMakeFiles/cpp-terminal.dir/base.cpp.o] Error 1 CMakeFiles/Makefile2:170: recipe for target 'cpp-terminal/CMakeFiles/cpp-terminal.dir/all' failed make[1]: *** [cpp-terminal/CMakeFiles/cpp-terminal.dir/all] Error 2 Makefile:145: recipe for target 'all' failed make: *** [all] Error 2

    opened by CharlesBunders 19
  • added y/n prompt

    added y/n prompt

    This PR adds some variations of a y/n style prompt.

    Todo:

    • [x] unify blocking + non blocking functions
    • [x] rename blocking to something else
    • [x] use COR
    • [x] fix windows compilation
    enhancement 
    opened by MCWertGaming 16
  • cpp-terminal road map

    cpp-terminal road map

    I have thought of some things, I would like to to do with cpp-terminal to make it better for it's purpose - providing a way to write cross-platform terminal applications. I thought of doing those steps in the particular order:

    1. Split cpp-terminal into smaller use case defined parts (#67)
    • [x] cpp-terminal/base.hpp which provides the raw functions to handle console input (basically the color functions and such)
    • [x] cpp-terminal/window.hpp which provides a manages "window", basically the window class
    • [x] cpp-terminal/input.hpp which provides the input functions
    • [x] cpp-terminal/prompt.hpp which provides the prompt and things like (#72)
    • [x] cpp-terminal/tools.hpp wich provides all function which are just meant for being used inside of the library
    • [x] restructure the header files (#117)
    1. Improve testing and similar
    • [x] #148
    • [ ] Improve stand-alone test (if needed) (#109)
    • [x] add github-action for checking if clang-format was applied (#92) -> add github actions for automatically applying clang-format (like with /bot apply-clang-format as issue comment)
    • [ ] add testing for the interactive prompt (#59)
    • [ ] enable more warnings and make them errors (#33)
    • [x] #149
    • [ ] use the doctest.h to a cmake or git sub module (#88)
    • [x] #150
    • [x] #151
    1. Improve the libraries core
    • [ ] merge window and window_24bit together (#96)
    • [ ] change the stand-alone color functions to always use the best, but supported color range -> fixes #108
    • [ ] change the window class to always chose the highest (but supported) color range -> fixes #107
    • [ ] add functions to determine ANSI code support by the current terminal (#102)
    • [x] make the window class specific functions also available as stand alone -> maybe even use those functions inside of the Window class to prevent duplicate code
    • [ ] fix the visual studio color problem (#95)
    • [ ] Improve the window class render() algorithm (#71)
    • [ ] #152
    • [ ] fix ALT+N (#118)
    • [ ] #153
    • [ ] let cpp-terminal handle most things it self #120
    1. Finishing up
    • [ ] create an actual documentation for cpp-terminal (#70)
    • [ ] document taken keys (#112)
    • [ ] add more exceptions to help developers on finding mistakes (#98)
    • [ ] add proper versioning (#90)
    • [ ] improve the readme (#75)
    • [ ] Document all tested terminals (#73)
    • [ ] maybe remove get_term_size_slow() (#115)
    • [ ] #154
    1. Extending the library
    • [ ] add more options for drawing
    • [ ] integrate proper resize handling into the window class (#94)
    • [ ] optimize the Window class for real time applications (games mostly)
    • [ ] #156
    • [ ] add syntax highlighting
    • [ ] #155
    • [ ] make the prompt more customizable for other developers
    • [ ] support Dear ImGUI #121

    I will make this Issue pinned on the repository and update it when needed to keep track on the things we want to do. Also for other peoples and possible contributors. Help is always appreciated!

    help wanted 
    opened by MCWertGaming 12
  • use pragma once instead of the include guard

    use pragma once instead of the include guard

    Hi! I have searched a bit around (not on stack overflow, because the answers are sometimes wrong or don't provide sources) and found some differences. The official C++ reference (here) notes:

    #pragma once is a non-standard pragma that is supported by the vast majority of modern compilers. If it appears in a header file, it indicates that it is only to be parsed once, even if it is (directly or indirectly) included multiple times in the same source file.

    and

    Unlike header guards, this pragma makes it impossible to erroneously use the same macro name in more than one file. On the other hand, since with #pragma once files are excluded based on their filesystem-level identity, this can't protect against including a header twice if it exists in more than one location in a project.

    The wikipedia article (here) notes:

    This approach minimally ensures that the contents of the include file are not seen more than once. This is more verbose, requires greater manual intervention, and is prone to programmer error as there are no mechanisms available to the compiler for prevention of accidental use of the same macro name in more than one file, which would result in only one of the files being included. Such errors are unlikely to remain undetected but can complicate the interpretation of a compiler error report. Since the pre-processor itself is responsible for handling #pragma once, the programmer cannot make errors which cause name clashes.

    In the absence of #include guards around #include directives, the use of #pragma once will improve compilation speed for some compilers since it is a higher-level mechanism; the compiler itself can compare filenames or inodes without having to invoke the C preprocessor to scan the header for #ifndef and #endif. Yet, since include guards appear very often and the overhead of opening files is significant, it is common for compilers to optimize the handling of include guards, making them as fast as #pragma once

    The Wikipedia article also notes, that all compilers are supporting it. I personally see, that both options provide their pros and cons, so it's maybe more of a personal preference. I know though, Microsoft is referring pragma once and made it the default in Visual Studio community. Jetbrains for example is using the default include guard by default.

    Greetings Damon

    opened by MCWertGaming 12
  • use sourcefile instead of 'just' headers

    use sourcefile instead of 'just' headers

    Hello,

    i want to ask, if I can split the header files of this library into header and source files. Yes, I am aware of the desciption advertising this library as a "header only library", but I see many disadvantages here.

    First of all we face many bad practices:

    • function bodies inside classes
    • internal enums and macros are exposed / included
    • includes get included into all projects (that mean users can't keep on track, wich libraries are inlcuded with the two headers(with include themselves as well) so all includes are also available inside the main project)
    • There are probably some more...

    And I want to note, that this library is quiet small and consist of two headers. I would like to move the source parts into a source file and keep the class and function definitions inside of the header file (we can even move both header together, something you were / are already planing to do, if I saw that right). As most people (hopefully) use cpp-terminal as cmake dependency, there wouldn't be anything different for them at all, if they just copy the files they would have to copy two or 3/4 files and add the source file/s to their compile target - I wouldn't call that much more complicated.

    Critizism is appreceated, it's an Idea - I hope this is not against the goal of this library!

    Best regards Damon

    opened by MCWertGaming 11
  • Add a make install target to install into a prefix

    Add a make install target to install into a prefix

    ... or use CMake? That would make it easy to have terminal on conda-forge, and we could use it right away in mamba.

    I just tested it and it looks very impressive and also very much like what we need so I am happy to help out here.

    opened by wolfv 10
  • WIP: Simplify the library

    WIP: Simplify the library

    Hello, I'll try to simplify the code of this library in this pull-request. The main changes are turning most inline function into macros, move ansi codes into macros (so everyone can understand what the code actually does, because ansi is not human readable). changing some code logic to be smaller and cleaner. This will take me a bit, so I will post an update if the pull-request is ready to merge! But feel free to add notes, if you notice something i should change.

    Best regards, Damon

    opened by MCWertGaming 9
  • Ci

    Ci

    Add new CI to test many compilers arcg etc... Old one has been kept for now with *_old.yml. I just activated the c++17 for now but the workflows are ready up to c++20

    opened by flagarde 8
  • Simplify platform.hpp

    Simplify platform.hpp

    We should simplify the platform.hpp structure. I would propose to do:

    • Mode.hpp
      • Setting the terminal into raw mode (function)
      • Reverting the Terminal into the previous state (function)
      • The Terminal state as platform universal struct
    • Additions.hpp
      • Functions that are used internally, like the UTF8 encoding / decoding
    enhancement 
    opened by MCWertGaming 2
  • Test windows performances

    Test windows performances

    @MCWertGaming You mentioned last time the Microsoft Windows terminal was quite slow.. Could you provide some code showing this ? I found some windows api to write to console... maybe it would be faster but this need some tests.

    opened by flagarde 9
  • Add test for multi GCC, CLang and intel

    Add test for multi GCC, CLang and intel

    Hello,

    I have created a workflow to test the library on many compiler version etc ... I have tried to push down the GCC and CLang required version. Please see https://github.com/flagarde/cpp-terminal/actions/workflows/ubuntu.yml

    Do you like to have a PR on this ? It would solve some of the #182

    opened by flagarde 22
  • add documentation for cmake install

    add documentation for cmake install

    We should add documentation on installing the library using cmake:

    • [ ] install on linux as shared library
    • [ ] use cpp-terminal as shared library on linux
    • [ ] install cpp-terminal as shared library on windows
    • [ ] using shared libraries on windows
      • [ ] place into shared include dir?
      • [ ] place next to the dll
      • [ ] static linking
      • [ ] other methods? Maybe visual studio stuff
    • [ ] MacOS specific things?
    • [ ] cross platform approach for managing cpp-terminal using cmake
      • [ ] git submodules
      • [ ] cmake managed
      • [ ] mamba?
      • [ ] other ways / package managers?
    documentation 
    opened by MCWertGaming 0
  • Fix include for cmake

    Fix include for cmake

    #196 changed the include from using <> to "". We should investigate if there is a case with cmake that actually requires "".

    enhancement 
    opened by MCWertGaming 1
Owner
Jupyter Xeus
Xeus: a C++ implementation of the Jupyter kernel protocol
Jupyter Xeus
Spitfire is a basic terminal language that can exicute code via the terminal.

Spitfire is a basic terminal language that can exicute code via the terminal. It is easy to learn and runs fast, considering that its just a 300 line c++ file.

jhomas tefferson 0 Nov 18, 2021
Library for creating terminal applications with text-based widgets

Library for creating terminal applications with text-based widgets FINAL CUT is a C++ class library and widget toolkit with full mouse support for cre

Markus Gans 724 Dec 30, 2022
Library for writing text-based user interfaces

IMPORTANT This library is no longer maintained. It's pretty small if you have a big project that relies on it, just maintain it yourself. Or look for

null 1.9k Dec 22, 2022
A simple header-only C++ argument parser library. Supposed to be flexible and powerful, and attempts to be compatible with the functionality of the Python standard argparse library (though not necessarily the API).

args Note that this library is essentially in maintenance mode. I haven't had the time to work on it or give it the love that it deserves. I'm not add

Taylor C. Richberger 1.1k Jan 4, 2023
A simple header-only C++ argument parser library. Supposed to be flexible and powerful, and attempts to be compatible with the functionality of the Python standard argparse library (though not necessarily the API).

args Note that this library is essentially in maintenance mode. I haven't had the time to work on it or give it the love that it deserves. I'm not add

Taylor C. Richberger 896 Aug 31, 2021
A C, C++ and Rust library to draw graphics with pixels in the terminal

A library to draw graphics with pixels in the terminal Who needs a GUI when you have a terminal ? Building To generate libpluto.a, run: $ make To ins

null 69 Nov 7, 2022
null 77 Dec 27, 2022
:computer: C++ Functional Terminal User Interface. :heart:

FTXUI Functional Terminal (X) User interface A simple C++ library for terminal based user interface. Demo: Feature Functional style. Inspired by [1] a

Arthur Sonzogni 4k Jan 3, 2023
A little UNIX-inspired terminal application for the Numworks Calculator (not using escher).

L.E. Terminal (let for short) is a little UNIX-inspired terminal for the Numworks Calculator.

Cacahuète Sans Sel 20 Aug 31, 2022
Draw sequence diagram in text from terminal.

sequence-diagram-cli Draw seqence diagram from terminal.

null 44 Dec 20, 2022
Terminal calculator made for programmers working with multiple number representations, sizes, and overall close to the bits

Programmer calculator The programmer calculator is a simple terminal tool designed to give maximum efficiency and flexibility to the programmer workin

romes 183 Dec 24, 2022
X terminal emulator rendering through OpenGL ES Compute Shaders

Zutty is a terminal emulator for the X Window System, functionally similar to several other X terminal emulators such as xterm, rxvt and countless others

Tom Szilagyi 259 Dec 24, 2022
The new Windows Terminal and the original Windows console host, all in the same place!

The new Windows Terminal and the original Windows console host, all in the same place!

Microsoft 86.8k Dec 29, 2022
n³ The unorthodox terminal file manager

n³ The unorthodox terminal file manager

Mischievous Meerkat 15.5k Jan 1, 2023
Graphs the activity of a chia harvester in a linux terminal.

Chia Harvest Graph Monitor for Chia Harvesting Introduction The chiaharvestgraph tool will graph Chia Harvesting activity in a linux terminal. Use a 2

Bram Stolk 215 Dec 11, 2022
a simple to use linux terminal

a simple to use linux terminal

notaweeb 7 Feb 17, 2022
Collection of human friendly terminal interface for git.

A collection of human friendly terminal user interface for git.

Arthur Sonzogni 76 Dec 30, 2022
Simple benchmark for terminal output

TermBench This is a simple timing utility you can use to see how slow your terminal program is at parsing escape-sequence-coded color output. It can b

Casey Muratori 174 Dec 4, 2022
tinytetris - 80x23 terminal tetris

tinytetris - 80x23 terminal tetris

Conor Taylor 2k Jan 3, 2023