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:

Issues
  • 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
  • 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
    • [ ] #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)
    • [ ] #149
    • [ ] use the doctest.h to a cmake or git sub module (#88)
    • [ ] #150
    • [ ] #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
  • Support 24bit colors

    Support 24bit colors

    Currently cpp-terminal only supports 4bit colors. We should extend it to also support 24 bit colors:

    https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit

    We have to add a new function color_24bit (similar to the current function color which is only 4 bit) and use the ESC[ 38;2;⟨r⟩;⟨g⟩;⟨b⟩ m sequence to set it.

    Then we need to extend the Window class, currently the color is only represented by the 3bit color vector<fg>. Instead we have to change it to vector<Color>, where the Color struct is something like this:

    struct Color {
        enum { bit24, bit3, bit4 } tag;
        union {
            struct {
                char R, G, B;
            };   
            fg color_fg;
            fgB color_fgB;
        };  
    };   
    

    and then the set_fg method would become:

    void set_fg(size_t x, size_t y, fg c) {
        m_fg[(y-1)*w+(x-1)].tag = bit3;    
        m_fg[(y-1)*w+(x-1)].color_fg = c;  
    }
    
    void set_fg_24bit(size_t x, size_t y, char R, char G, char B) {
        m_fg[(y-1)*w+(x-1)].tag = bit24;
        m_fg[(y-1)*w+(x-1)].R = R;      
        m_fg[(y-1)*w+(x-1)].G = G;
        m_fg[(y-1)*w+(x-1)].B = B;
    }
    

    Also the render method would need to be updated.

    enhancement 
    opened by certik 8
  • Adding dotfiles

    Adding dotfiles

    This pull-request ties together #76 and #30 by adding the default .gitignore provided by github here and all config files used by visual studio community, visual studio code, Jetbrains CLion and generated files by cmake as standalone. clang-format will follow as well. The discussing is ongoing in #30.

    enhancement 
    opened by MCWertGaming 6
  • coloring fix on macos

    coloring fix on macos

    I have dicussed with @certik that mixing styles and colors by doing the ansi codes adter each other (like \033[34m\033[1m) we should combine them like \033[1;34m which should create the same output (bold blue) than the first and longer escape code. Besides that this would tighten the menu class rendering, I'm wondering if it also fixes the color issue on macos.

    @certik can you confirm that? I have still no macos device on my hand.

    enhancement 
    opened by MCWertGaming 1
  • Find background color

    Find background color

    I am trying to write a small helper to find the background / foreground colors of a terminal. I can get hexadecimal values by asking echo "\e]11;?\a"

    I tried to implement this:

    void Term::get_background_color() {
        char buf[32];
        write("\e]11;?\a");
        for (unsigned int i = 0; i < sizeof(buf) - 1; i++) {
            while (!Private::read_raw(&buf[i]))
                ;
            std::cout << "read [" << i << "] " << buf[i] << std::endl;
        }
    }
    

    But it seems to block. Any ideas? @certik this is for mamba/micromamba progress bars :)

    enhancement 
    opened by wolfv 4
  • old TODO's and clean up

    old TODO's and clean up

    We need to implement all TODO's and clean up the code a bit:

    • [ ] move constants in conversion.hpp into a enum
    • [ ] change linux sscanf to something better (clang-tidy issue) (TODO)
    • [ ] Silently disable raw mode if the terminal is not interactive (TODO)
    • [ ] skip adding a number to ansi codes on 0 (0 is the deafult value if no value is specified)
    • [ ] throw runtime errors on wrong values for ANSI functions
    • [ ] optimize datatypes (use for 0 - 255 ranges uint8_t)
    • [ ] make the code available for unsupported sequences in input function
    • [ ] merge window and window24 together
    • [ ] add screen parameter to window class (TODO)
    • [ ] make a function out of the ansi code from the new prompt
    • [ ] remove function names from headers
    enhancement 
    opened by MCWertGaming 0
  • Finishing header split

    Finishing header split

    This are some things that still need to be done to finish the header spliting:

    • [ ] Implement Term::Private::read_raw() second functionality again if no keyboard was initialized
    • [ ] make Term::Private::BaseTerminal a function
    • [ ] remove platform header from the base.hpp header
    • [ ] remove obsolete comments
    • [ ] remove undefinition from the prompt.hpp header
    enhancement 
    opened by MCWertGaming 0
  • Tab auto-completion feature

    Tab auto-completion feature

    Can you add tab auto-completion feature to terminal.cpp, Term:prompt?

    If one wants a tab auto-completion, the only possible way is copying terminal.cpp codes and add "case Key::TAB:" by oneself. It would be nice if tab auto-completion is provided by default in prompt sample code.

    enhancement 
    opened by woojung3 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 625 May 11, 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.8k May 5, 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 963 May 2, 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 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 67 Apr 9, 2022
null 76 Apr 18, 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 3k May 12, 2022
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 18 Apr 27, 2022
Draw sequence diagram in text from terminal.

sequence-diagram-cli Draw seqence diagram from terminal.

null 42 Feb 28, 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 164 May 8, 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 207 May 12, 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 83.1k May 16, 2022
n³ The unorthodox terminal file manager

n³ The unorthodox terminal file manager

Mischievous Meerkat 14.1k May 17, 2022
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 219 May 2, 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 60 May 5, 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 169 May 9, 2022
tinytetris - 80x23 terminal tetris

tinytetris - 80x23 terminal tetris

Conor Taylor 1.6k May 9, 2022