aboutsummaryrefslogblamecommitdiffstatshomepage
path: root/test/unit_tests/shared/console.hpp
blob: 48d1faae4a615207112983ef16a53318ee64596b (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16















                                                                 
                  

                  
                    









                                            
                                                                                 
 
                                                                                                
 
                                                           
 
                                                         
 
                                                                           
 
                                                                         



























                                                                              

                                                                            
                                          
                       
                                                

                                                           


                                   
                                               























                                                                                          
                                                   
                                    
                                                         







                                                                                   



                                                                                 



                                                                        
                                                                          

















                                                                                       
// Copyright (c) 2020 Egor Tensin <Egor.Tensin@gmail.com>
// This file is part of the "winapi-common" project.
// For details, see https://github.com/egor-tensin/winapi-common.
// Distributed under the MIT License.

#pragma once

#include <winapi/error.hpp>
#include <winapi/handle.hpp>
#include <winapi/utf8.hpp>

#include <boost/algorithm/string.hpp>

#include <windows.h>

#include <cstddef>
#include <cstdint>
#include <cstring>
#include <sstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

namespace console {

class Buffer {
public:
    typedef CONSOLE_SCREEN_BUFFER_INFO Info;

    Buffer() : m_handle(winapi::Handle::std_out()), m_info(get_info(m_handle)) {}

    Buffer(winapi::Handle&& handle) : m_handle(std::move(handle)), m_info(get_info(m_handle)) {}

    int16_t get_columns() const { return m_info.dwSize.X; }

    int16_t get_lines() const { return m_info.dwSize.Y; }

    int16_t get_cursor_column() const { return m_info.dwCursorPosition.X; }

    int16_t get_cursor_line() const { return m_info.dwCursorPosition.Y; }

    void update() { m_info = get_info(m_handle); }

    /*
     * This is a stupid little function to read the console screen buffer.
     * It's fragile and will break whenever anything happens (like, if the
     * console screen is resized).
     *
     * The screen buffer:
     * 1) doesn't preserve line breaks,
     * 2) pads text lines with spaces (ASCII 0x20) for storage.
     *
     * Hence, the "lines" read are one-dimensional arrays, right-trimmed, and
     * there's no way to learn whether the whole line printed by the user was
     * read back in its entirety.
     *
     * For example, let the console window be 80 columns wide, and the user
     * prints 85 consecutive characters 'a' using
     *
     *     std::cout << std::string(85, 'a') << '\n';
     *
     * The following holds:
     * 1) read_lines(-1, -1) == {"aaaaa"},
     * 2) read_lines(-2, -2) == {std::string(80, 'a'), "aaaaa"}.
     *
     * I also don't know how it interacts with tab characters '\t', encodings,
     * etc. It sucks, don't use it.
     */
    std::vector<std::string> read_lines(int16_t top, int16_t bottom) const {
        if (top < 0)
            top = get_cursor_line() + top;
        if (bottom < 0)
            bottom = get_cursor_line() + bottom;
        if (top < 0 || bottom < 0)
            throw std::range_error{"Invalid console line"};
        if (top > bottom) {
            std::swap(top, bottom);
        }
        int16_t numof_lines = bottom - top + 1;

        COORD buffer_size;
        buffer_size.X = get_columns();
        buffer_size.Y = numof_lines;

        COORD buffer_coord;
        buffer_coord.X = 0;
        buffer_coord.Y = 0;

        std::vector<CHAR_INFO> buffer;
        buffer.resize(buffer_size.X * buffer_size.Y);

        SMALL_RECT read_region;
        read_region.Top = top;
        read_region.Left = 0;
        read_region.Bottom = bottom;
        read_region.Right = buffer_size.X - 1;

        if (!::ReadConsoleOutputW(
                m_handle.get(), buffer.data(), buffer_size, buffer_coord, &read_region)) {
            throw winapi::error::windows(GetLastError(), "ReadConsoleOutputW");
        }

        std::vector<std::string> result;
        for (int16_t i = 0; i < numof_lines; ++i) {
            std::wostringstream oss;
            for (int16_t c = 0; c < buffer_size.X; ++c) {
                oss << buffer[i * buffer_size.X + c].Char.UnicodeChar;
            }
            result.emplace_back(boost::trim_right_copy(winapi::narrow(oss.str())));
        }

        return result;
    }

    std::vector<std::string> read_last_lines(std::size_t numof_lines = 1) const {
        if (numof_lines < 1 || numof_lines > INT16_MAX)
            throw std::range_error{"Invalid number of lines"};
        return read_lines(-static_cast<int16_t>(numof_lines), -1);
    }

    std::string read_last_line() const { return read_lines(-1, -1)[0]; }

    std::string read_line(int16_t n) const { return read_lines(n, n)[0]; }

private:
    static Info get_info(const winapi::Handle& handle) {
        Info dest;
        std::memset(&dest, 0, sizeof(dest));

        if (!::GetConsoleScreenBufferInfo(static_cast<HANDLE>(handle), &dest)) {
            throw winapi::error::windows(GetLastError(), "GetConsoleScreenBufferInfo");
        }

        return dest;
    }

    winapi::Handle m_handle;
    Info m_info;
};

} // namespace console