<|start_filename|>Console/inc/Console/tty/TermcapMap.h<|end_filename|>
/*
FreeBSD License

Copyright (c) 2020-2021 vikonix: <EMAIL>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "Console/KeyCodes.h"
#include "Console/Types.h"
//#include "termdb/termdb.hpp"

#ifndef WIN32
    #include <term.h>
    #include <cstdlib>
#endif

//////////////////////////////////////////////////////////////////////////////
namespace _Console
{

enum ScreenCapType
{
    S_ClrScr = 0,
    S_Beep,

    S_CursorNormal,
    S_CursorHide,
    S_CursorBold,

    S_GotoXY,
    S_CursorHome,
    S_CursorBegStr,
    S_CursorLeft,
    S_CursorRight,
    S_CursorUp,
    S_CursorDown,

    S_SetTextColor,
    S_SetFonColor,
    S_ColorBold,
    S_Reverse,
    S_Normal,

    S_InsertChar,
    S_InsertCharN,
    S_DelChar,
    S_DelCharN,
    S_DelBegOfStr,
    S_DelEndOfStr,

    S_DeleteL,
    S_DeleteLN,
    S_InsertL,
    S_InsertLN,

    S_SetScroll,
    S_InsertMode,
    S_EInsertMode,

    S_AltCharPairs,
    S_AltCharBegin,
    S_AltCharEnd,
    S_AltCharEnable,

    S_TermInit,
    S_TermReset,
    S_DefaultColor,

    CAP_NUMBER
};

//////////////////////////////////////////////////////////////////////////////
struct KeyCap
{
    const char*     id{};
    input_t         code{};
};

struct ScreenCap 
{
    const char*     id{};
    ScreenCapType   cap{};
    const char*     sDefCap{};
};

//////////////////////////////////////////////////////////////////////////////
extern std::list<KeyCap>    g_keyCap;
extern std::list<ScreenCap> g_screenCap;

#ifndef WIN32

class TermcapBuffer
{
public:
    static TermcapBuffer& getInstance()
    {
        static TermcapBuffer instance;
        return instance;
    }

    void LoadTermcap() const
    {
        static char termcapBuff[0x2000];
        char* term;
        if (NULL == (term = getenv("TERM")))
            return;

        if (1 != tgetent(termcapBuff, term))
            return;
    }

    TermcapBuffer(const TermcapBuffer&) = delete;
    void operator= (const TermcapBuffer&) = delete;

private:
    TermcapBuffer() {LoadTermcap();}
};

} //namespace _Console

#endif //WIN32




<|start_filename|>Console/src/tty/ScreenTTY.cpp<|end_filename|>
/*
FreeBSD License

Copyright (c) 2020-2021 vikonix: <EMAIL>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef WIN32

#include "utils/logger.h"
#include "utfcpp/utf8.h"
#include "Console/tty/ScreenTTY.h"
#include "Console/ScreenBuffer.h"

#include <errno.h>
#include <unistd.h>
#include <pwd.h>
#include <term.h>
#include <sys/ioctl.h>

#include <iomanip>
#include <filesystem>

using namespace _Utils;

namespace _Console
{

bool ScreenTTY::Init()
{
    if(m_stdout > 0)
        return true;

    //get file handler
    m_stdout = fileno (stdout);

    //load termcap
    for(const auto& cap: g_screenCap)
    {
        ScreenCapType type = cap.cap;
        if(m_cap[type].str.empty())
        {
            char buff[100];
            char* pbuff = buff;

            char* str = tgetstr(const_cast<char*>(cap.id), &pbuff);
            if(nullptr != str)
            {
                //load this cap
                m_cap[type].str = str;
                m_cap[type].id = cap.id;
            }
            else if(*cap.sDefCap)
            {
                //default cap
                m_cap[type].str = cap.sDefCap;
                m_cap[type].id = "--";
            }
        }
    }
    
    GetScreenSize(m_sizex, m_sizey);

    char* term = getenv("TERM");
    if(!strncmp(term, "xterm", 5))
        m_fXTERMconsole = true;
    if(nullptr != strstr(term, "256"))
        m_256colors = true;
    
    LOG(DEBUG) << "term x=" << m_sizex << " y=" << m_sizey
        << " xterm=" << m_fXTERMconsole << " 256=" << m_256colors;

    for(int i = 0; i < CAP_NUMBER; ++i)
        if(!m_cap[i].str.empty())
            LOG(DEBUG) << " Cap[" << i << "][" << m_cap[i].id << "]" << CastEscString(m_cap[i].str);

    //save parameters and use alternative screen buffer
    std::string cmd { "\0337\x1b[?47h"};
    write(m_stdout, cmd.c_str(), cmd.size());

    [[maybe_unused]] bool rc = _WriteStr(m_cap[S_AltCharEnable].str)
        && Flush();

    if(!strncmp(term, "vt100", 5))
    {
        //cut $<2> or $<5>
        std::string str = m_cap[S_GotoXY].str;
        size_t len = str.size();
        if(len > 5 && str[len - 4] == '$' && str[len - 3] == '<')
            m_cap[S_GotoXY].str.resize(len - 4);

        str = m_cap[S_ColorBold].str;
        len = str.size();
        if(len > 5 && str[len - 4] == '$' && str[len - 3] == '<')
            m_cap[S_ColorBold].str.resize(len - 4);

        str = m_cap[S_Normal].str;
        len = str.size();
        if(len > 5 && str[len - 4] == '$' && str[len - 3] == '<')
            m_cap[S_Normal].str.resize(len - 4);
    }

  return true;
}


void ScreenTTY::Deinit()
{
    if(m_stdout <= 0)
        return;

    std::string param = m_cap[S_TermReset].str;
    if(!param.empty())
    {
        write(m_stdout, param.c_str(), param.size());
    }

    //use normal screen buffer and restore parameters
    param = "\x1b[?47l\0338\x1b[0m";
    write(m_stdout, param.c_str(), param.size());

    //set default foreground/background
    param = m_cap[S_DefaultColor].str;
    if(!param.empty())
    {
        write(m_stdout, param.c_str(), param.size());
    }

    m_stdout = -1;
}


//////////////////////////////////////////////////////////////////////////////
bool ScreenTTY::GetScreenSize(pos_t& sizex, pos_t& sizey) const
{
    pos_t x = 0;
    pos_t y = 0;

#ifdef TIOCGWINSZ
    winsize win_struct;

    do
    {
        if ((ioctl(1, TIOCGWINSZ, &win_struct) == 0)
         || (ioctl(0, TIOCGWINSZ, &win_struct) == 0)
         || (ioctl(2, TIOCGWINSZ, &win_struct) == 0))
        {
            x = static_cast<pos_t>(win_struct.ws_col);
            y = static_cast<pos_t>(win_struct.ws_row);
            break;
        }
    }
    while (errno == EINTR);
    LOG(DEBUG) << "screen size (1) x=" << x << " y=" << y;
#endif

    if(x <= 0 || y <= 0)
    {
        char* str = getenv("COLUMNS");
        if(nullptr != str)
            x = atoi(str);

        str = getenv("LINES");
        if(nullptr != str)
            y = atoi(str);

        LOG(DEBUG) << "screen size (2) x=" << x << " y=" << y;
    }

    if(x <= 0 || y <= 0)
    {
        x = tgetnum("co");
        y = tgetnum("li");
        LOG(DEBUG) << "screen size (3) x=" << x << " y=" << y;
    }

    if(x <= 0)
        x = 80;
    if(y <= 0)
        y = 24;

    if(x > MAX_COORD)
        x = MAX_COORD;//max x size
    if(y > MAX_COORD)
        y = MAX_COORD;//max y size

    LOG(DEBUG) << "screen size x=" << x << " y=" << y;
    sizex = x;
    sizey = y;

    return true;
}


bool ScreenTTY::WriteConsoleTitle(const std::string& title)
{
    if(m_stdout <= 0)
        return false;

    //for XTERM
    if(m_fXTERMconsole)
    {
        std::string str {"\x1b]0;" + title + "\7"};
        write(m_stdout, str.c_str(), str.size());
    }

    return true;
}


bool ScreenTTY::GotoXY(pos_t x, pos_t y)
{
    if(m_stdout <= 0)
        return false;
    if(m_cap[S_GotoXY].str.empty())
        return false;

    m_posx = x;
    m_posy = y;

    return _WriteStr(tgoto(m_cap[S_GotoXY].str.c_str(), x, y));
}


bool ScreenTTY::SetCursor(cursor_t cursor)
{
    if(m_cursor == cursor)
        return true;

    std::string_view cap;
    switch(cursor)
    {
    case cursor_t::CURSOR_HIDE:
        cap = m_cap[S_CursorHide].str;
        break;
    case cursor_t::CURSOR_NORMAL:
        cap = m_cap[S_CursorNormal].str;
        break;
    case cursor_t::CURSOR_OVERWRITE:
        if(!m_cap[S_CursorBold].str.empty())
            cap = m_cap[S_CursorBold].str;
        else
            cap = m_cap[S_CursorNormal].str;
        break;
    default:
        return false;
    }
    
    m_cursor = cursor;
    
    int rc = _WriteStr(std::string{cap})
    && Flush();
    
    return rc;
}


bool ScreenTTY::SetTextAttr(color_t color)
{
    if(m_color == color)
        return true;
    if(m_stdout <= 0)
        return false;

    //LOG(DEBUG) << "SetTextAttr " << std::hex << color << std::dec;
    bool rc = true;

    if((color & TEXT_BRIGHT) != (m_color & TEXT_BRIGHT))
    {
        if((color & TEXT_BRIGHT) == 0)
            rc = _WriteStr(m_cap[S_Normal].str);
        else
            rc = _WriteStr(m_cap[S_ColorBold].str);
    }
    
    if(TEXT_COLOR(color) != TEXT_COLOR(m_color) || FON_COLOR(color) != FON_COLOR(m_color))
    {
        if(!m_256colors)
        {
            if(!m_cap[S_SetTextColor].str.empty())
                rc = _WriteStr(tgoto(m_cap[S_SetTextColor].str.c_str(), 0, COLOR_CHANGE(TEXT_COLOR(color))));
            if(!m_cap[S_SetFonColor].str.empty())
                rc = _WriteStr(tgoto(m_cap[S_SetFonColor].str.c_str(), 0, COLOR_CHANGE(FON_COLOR(color))));
        }
        else
        {
            int text = COLOR_CHANGE(TEXT_COLOR(color));
            if(0 != (color & TEXT_BRIGHT))
                text += 8;
            rc = _WriteStr(tgoto("\x1b[38;5;%dm", 0, text));
            rc = _WriteStr(tgoto("\x1b[48;5;%dm", 0, COLOR_CHANGE(FON_COLOR(color))));
        }
    }

    m_color = color;
    return rc;
}


bool ScreenTTY::Flush()
{
    if(m_stdout <= 0)
        return false;
    if(m_OutBuff.empty())
        return true;

    int rc;
    while(-1 == (rc = write(m_stdout, m_OutBuff.c_str(), m_OutBuff.size())))
    {
        if(errno == EINTR)
            continue;

        if(errno == EAGAIN)
        {
            sleep(1);
            continue;
        }

        break;
    }

    //LOG(DEBUG) << "Flush buff size=" << m_OutBuff.size() << "->" << rc;
    //LOG(DEBUG) << CastEscString(m_OutBuff);
    m_OutBuff.clear();

    return rc > 0;
}


bool ScreenTTY::_WriteStr(const std::string& str)
{
    m_OutBuff += str;
    
    if(m_OutBuff.size() >= OUTBUFF_SIZE)
        return Flush();

    return true;
}


bool ScreenTTY::_WriteChar(char c)
{
    m_OutBuff += c;

    if(m_OutBuff.size() >= OUTBUFF_SIZE)
        return Flush();

    return true;
}


bool ScreenTTY::_WriteWChar(char16_t wc)
{
    bool rc;

    if(wc == 0)
        return true;
    else if(wc < ACS_MAX)
    {
        //Alt char set
        std::string str;
        utf8::append(m_ACS[wc], str);
        rc = _WriteStr(str.c_str());
    }
    else 
    {    
        if(wc < 0x80)
        {
            rc = _WriteChar(wc);
        }
        else
        {
            std::string str;
            utf8::append(wc, str);
            rc = _WriteStr(str.c_str());
        }
    }

    if(++m_posx >= m_sizex)
    {
        m_posx = 0;
        if(m_posy < m_sizey - 1)
            ++m_posy;
    }

    return rc;
}


bool ScreenTTY::SetSize(pos_t sizex, pos_t sizey)
{
    m_sizex = sizex;
    m_sizey = sizey;

    return true;
}


bool ScreenTTY::WriteChar(char16_t c)
{
    if(m_stdout <= 0)
        return false;

    return _WriteWChar(c);
}


bool ScreenTTY::WriteStr(const std::u16string& str)
{
    if(m_stdout <= 0)
        return false;

    for(auto c : str)
    {
        bool rc = _WriteWChar(c);
        if(!rc)
            return false;
    }

    return true;
}


bool ScreenTTY::Beep()
{
    bool rc;
    if(!m_cap[S_Beep].str.empty())
        rc = _WriteStr(m_cap[S_Beep].str.c_str());
    else
        rc = _WriteChar(7);
    rc = Flush();
    return rc;
}


bool ScreenTTY::WriteLastChar(char16_t prevC, char16_t lastC)
{
    if(m_stdout <= 0)
        return false;

    //LOG(DEBUG) << "WriteLastChar " << std::hex << prevC << " " << lastC << std::dec;

    bool rc = GotoXY(m_sizex - 2, m_sizey - 1)
    && WriteChar(lastC)
    && GotoXY(m_sizex - 2, m_sizey - 1)
    && _WriteStr(m_cap[S_InsertMode].str);

    if(m_cap[S_InsertMode].str.empty())
        rc = _WriteStr(m_cap[S_InsertChar].str);

    rc = WriteChar(prevC)
    && _WriteStr(m_cap[S_EInsertMode].str);

    return rc;
}

bool ScreenTTY::ScrollBlock(pos_t left, pos_t top, pos_t right, pos_t bottom,
    pos_t n, scroll_t mode, uint32_t* invalidate)
{
    //LOG(DEBUG) << "scroll m=" << static_cast<int>(mode) << " l=" << left << " t=" << top << " r=" << right << " b=" << bottom << " n=" << n;

    bool rc = false;
    std::string cap = m_cap[S_SetScroll].str;

    switch(mode)
    {
    case scroll_t::SCROLL_UP:
        if(bottom - top <= n)
            break;

        if(invalidate)
            *invalidate = INVALIDATE_LEFT | INVALIDATE_RIGHT;

        if(!cap.empty())
            //fix up/down scroll region
            rc = _WriteStr(tgoto(cap.c_str(), bottom, top));

        rc = GotoXY(0, top);
        if(!m_cap[S_DeleteLN].str.empty())
            rc = _WriteStr(tgoto(m_cap[S_DeleteLN].str.c_str(), 0, n));
        else
            for(pos_t i = 0; i < n; ++i)
                rc = _WriteStr(m_cap[S_DeleteL].str.c_str());

        if(!cap.empty())
            rc = _WriteStr(tgoto(cap.c_str(), m_sizey - 1, 0));
        else
        {
            //if fix impossible then insert bottom lines
            rc = GotoXY(0, bottom);
            if(!m_cap[S_InsertLN].str.empty())
                rc = _WriteStr(tgoto(m_cap[S_InsertLN].str.c_str(), 0, n));
            else
                for(pos_t i = 0; i < n; ++i)
                    rc = _WriteStr(m_cap[S_InsertL].str.c_str());
        }
        break;

    case scroll_t::SCROLL_DOWN:
        if(bottom - top <= n)
            break;

        if(invalidate)
            *invalidate = INVALIDATE_LEFT | INVALIDATE_RIGHT;

        if(!cap.empty())
            //fix up/down scroll region
            rc = _WriteStr(tgoto(cap.c_str(), bottom, top));
        else
        {
            //if fix impossible then delete bottom lines before
            rc = GotoXY(0, bottom);
            if(!m_cap[S_DeleteLN].str.empty())
                rc = _WriteStr(tgoto(m_cap[S_DeleteLN].str.c_str(), 0, n));
            else
                for(pos_t i = 0; i < n; ++i)
                    rc = _WriteStr(m_cap[S_DeleteL].str.c_str());
        }

        rc = GotoXY(0, top);
        if(!m_cap[S_InsertLN].str.empty())
            rc = _WriteStr(tgoto(m_cap[S_InsertLN].str.c_str(), 0, n));
        else
            for(pos_t i = 0; i < n; ++i)
                rc = _WriteStr(m_cap[S_InsertL].str.c_str());

        if(!cap.empty())
            rc = _WriteStr(tgoto(cap.c_str(), m_sizey - 1, 0));
        break;

    case scroll_t::SCROLL_LEFT:
    {
        if(right - left <= n)
            break;

        if(invalidate)
            *invalidate = 0;

        rc = _WriteStr(m_cap[S_InsertMode].str.c_str());

        for(pos_t i = top; i <= bottom; ++i)
        {
            rc = GotoXY(left, i);

            if(n > 1 && !m_cap[S_DelCharN].str.empty())
                rc = _WriteStr(tgoto(m_cap[S_DelCharN].str.c_str(), 0, n));
            else
                for(pos_t j = 0; j < n; ++j)
                    rc = _WriteStr(m_cap[S_DelChar].str.c_str());

            //now insert right chars
            rc = GotoXY(right - n + 1, i);

            if(!m_cap[S_InsertMode].str.empty())
            {
                std::string buff(n, ' ');
                rc = _WriteStr(buff);
            }
            else if(!m_cap[S_InsertChar].str.empty())
                for(pos_t j = 0; j < n; ++j)
                    rc = _WriteStr(m_cap[S_InsertChar].str.c_str());
        }

        rc = _WriteStr(m_cap[S_EInsertMode].str.c_str());
        break;
    }

    case scroll_t::SCROLL_RIGHT:
    {
        if(right - left <= n)
            break;

        if(invalidate)
            *invalidate = 0;

        rc = _WriteStr(m_cap[S_InsertMode].str.c_str());

        for(pos_t i = top; i <= bottom; ++i)
        {
            //before delete right chars
            rc = GotoXY(right - n, i);

            if(n > 1 && !m_cap[S_DelCharN].str.empty())
                rc = _WriteStr(tgoto(m_cap[S_DelCharN].str.c_str(), 0, n));
            else
                for(pos_t j = 0; j < n; ++j)
                    rc = _WriteStr(m_cap[S_DelChar].str.c_str());

            rc = GotoXY(left, i);

            if(!m_cap[S_InsertMode].str.empty())
            {
                std::string buff(n, ' ');
                rc = _WriteStr(buff);
            }
            else if(!m_cap[S_InsertChar].str.empty())
                for(pos_t j = 0; j < n; ++j)
                    rc = _WriteStr(m_cap[S_InsertChar].str.c_str());
        }

        rc = _WriteStr(m_cap[S_EInsertMode].str.c_str());
        break;
    }

    default:
        return false;
    }

    return rc;
}

bool ScreenTTY::WriteBlock(
    pos_t left, pos_t top, pos_t right, pos_t bottom,
    const ScreenBuffer& block, pos_t xoffset, pos_t yoffset)
{
    bool rc = false;

    //LOG(DEBUG) << "WriteBlock l=" << left << " t=" << top << " r=" << right << " b=" << bottom;
    
    bool fLast {false};
    if(right == m_sizex - 1 && bottom == m_sizey - 1)
        fLast = 1;

    color_t color {0};
    pos_t sizex = right - left + 1;
    pos_t sizey = bottom - top + 1;
    for(pos_t y = 0; y < sizey; ++y)
    {
        GotoXY(left, top + y);
        for(pos_t x = 0; x < sizex; ++x)
        {
            cell_t c = block.GetCell(xoffset + x, yoffset + y);
            color_t a = GET_CCOLOR(c);
            char16_t ch = GET_CTEXT(c);
            if(color != a)
            {
                //LOG(DEBUG) << "x=" << x << " y=" << y << " a=" << std::hex << a << std::dec;
                rc = SetTextAttr(a);
                color = a;
            }
            
            //LOG(DEBUG) << "x=" << x << " y=" << y << " ch=" << std::hex << ch << std::dec;
            rc = _WriteWChar(ch);
            
            if(fLast && y == sizey - 1 && x == sizex - 2)
                break;
        }
    }

    if(fLast)
    {
        rc = WriteLastChar(
            GET_CTEXT(block.GetCell(m_sizex - 2, m_sizey - 1)), 
            GET_CTEXT(block.GetCell(m_sizex - 1, m_sizey - 1)));
    }

    rc = Flush();
    return rc;
}

} //namespace _Console

#endif //WIN32


<|start_filename|>Console/src/tty/TermcapMap.cpp<|end_filename|>
/*
FreeBSD License

Copyright (c) 2020-2021 vikonix: <EMAIL>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef WIN32

#define TERMDB_IMPLEMENTATION
#include "Console/tty/TermcapMap.h"
#include "Console/KeyCodes.h"


//////////////////////////////////////////////////////////////////////////////
namespace _Console
{

std::list<KeyCap> g_keyCap
{
  {"kb", K_BS},

  {"ku", K_UP},
  {"kd", K_DOWN},
  {"kr", K_RIGHT},
  {"kl", K_LEFT},
  {"kh", K_HOME},
  {"kH", K_END},
  {"kP", K_PAGEUP},
  {"kN", K_PAGEDN},
  {"kI", K_INSERT},
  {"kD", K_DELETE},

  {"k1", K_F1},
  {"k2", K_F2},
  {"k3", K_F3},
  {"k4", K_F4},
  {"k5", K_F5},
  {"k6", K_F6},
  {"k7", K_F7},
  {"k8", K_F8},
  {"k9", K_F9},
  {"k;", K_F10},
  {"F1", K_F1  | K_SHIFT},
  {"F2", K_F2  | K_SHIFT},
  {"F3", K_F3  | K_SHIFT},
  {"F4", K_F4  | K_SHIFT},
  {"F5", K_F5  | K_SHIFT},
  {"F6", K_F6  | K_SHIFT},
  {"F7", K_F7  | K_SHIFT},
  {"F8", K_F8  | K_SHIFT},
  {"F9", K_F9  | K_SHIFT},
  {"FA", K_F10 | K_SHIFT}
};


//////////////////////////////////////////////////////////////////////////////
std::list<ScreenCap> g_screenCap
{
  {"cl", S_ClrScr,        "\x1b[2J\x1b[H"},
  {"bl", S_Beep,          ""},

  {"ve", S_CursorNormal,  ""},
  {"vi", S_CursorHide,    ""},
  {"vs", S_CursorBold,    ""},

  {"cm", S_GotoXY,        "\x1b[%i%d;%dH"},
  {"ho", S_CursorHome,    ""},
  {"cr", S_CursorBegStr,  "\xd"},
  {"bc", S_CursorLeft,    ""},
  {"le", S_CursorLeft,    "\x8"},
  {"nd", S_CursorRight,   ""},
  {"up", S_CursorUp,      ""},
  {"do", S_CursorDown,    ""},
  {"nl", S_CursorDown,    "\xa"},

  {"AF", S_SetTextColor,  ""},
  {"Sf", S_SetTextColor,  "\x1b[3%dm"},
  {"AB", S_SetFonColor,   ""},
  {"Sb", S_SetFonColor,   "\x1b[4%dm"},
  {"md", S_ColorBold,     ""},
  {"mr", S_Reverse,       ""},
  {"me", S_Normal,        ""},

  {"ic", S_InsertChar,    ""},
  {"IC", S_InsertCharN,   ""},
  {"dc", S_DelChar,       ""},
  {"DC", S_DelCharN,      ""},
  {"cb", S_DelBegOfStr,   ""},
  {"ce", S_DelEndOfStr,   ""},

  {"dl", S_DeleteL,       ""},
  {"DL", S_DeleteLN,      ""},
  {"al", S_InsertL,       ""},
  {"AL", S_InsertLN,      ""},

  {"cs", S_SetScroll,     ""},
  {"im", S_InsertMode,    ""},
  {"ei", S_EInsertMode,   ""},

  {"ac", S_AltCharPairs,  "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~"},
  {"as", S_AltCharBegin,  ""},
  {"ae", S_AltCharEnd,    ""},
  {"eA", S_AltCharEnable, ""},

  {"ti", S_TermInit,      ""},
  {"te", S_TermReset,     ""},
  {"op", S_DefaultColor,  "\x1b[0;39;49m"}
};

} //namespace _Console

#endif //WIN32

<|start_filename|>Editor/inc/Editor.h<|end_filename|>
/*
FreeBSD License

Copyright (c) 2020-2021 vikonix: <EMAIL>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once

#include "utils/MemBuff.h"
#include "Console/Types.h"
#include "UndoList.h"
#include "WndManager/Wnd.h"
#include "LexParser.h"

#include <unordered_set>
#include <filesystem>
#include <limits>
#include <algorithm>
#include <functional>


#if !defined(__APPLE__) && !defined(__FreeBSD__)
using searcher_t = std::boyer_moore_horspool_searcher<std::u16string::const_iterator>;
using rsearcher_t = std::boyer_moore_horspool_searcher<std::u16string::const_reverse_iterator>;
#else
using searcher_t = std::default_searcher<std::u16string::const_iterator>;
using rsearcher_t = std::default_searcher<std::u16string::const_reverse_iterator>;
#endif

using namespace _Utils;
using namespace _WndManager;

namespace _WndManager
{
class FrameWnd;
}
namespace iconvpp
{
class CpConverter;
}

namespace _Editor
{

enum class eol_t
{
    unix_eol,
    win_eol,
    mac_eol
};

enum class file_state
{
    not_changed,
    changed,
    removed
};

#ifdef WIN32
    #define DEF_EOL eol_t::win_eol
#else
    #define DEF_EOL eol_t::unix_eol
#endif

/////////////////////////////////////////////////////////////////////////////
constexpr size_t    STR_NOTDEFINED{ std::numeric_limits<size_t>::max() };
constexpr uintmax_t MAX_PARSED_SIZE{ 0x2000000 }; // 32 MB

constexpr size_t    c_buffsize{ 0x200000 };//2MB
using read_buff_t = std::array<char, c_buffsize>;

class Editor
{
    inline const static std::string     c_utf8Bom{ "\xef\xbb\xbf" };
    inline const static std::u16string  c_utf16Bom{ u"\xfeff" };

private:
    std::shared_ptr<iconvpp::CpConverter>       m_converter;

    std::filesystem::path                       m_file;
    std::filesystem::file_time_type             m_fileTime{};
    uintmax_t                                   m_fileSize{};
    MemStrBuff<std::string, std::string_view>   m_buffer;

    std::unordered_set<FrameWnd*>               m_wndList;

    UndoList        m_undoList;
    LexParser       m_lexParser;

    //config variables
    std::string     m_cp{};
    size_t          m_maxStrlen{0x800};
    eol_t           m_eol{DEF_EOL};
    size_t          m_tab{8};   //tab position
    bool            m_saveTab{};//save tab or not
    bool            m_showTab{};
    bool            m_ro{};
    bool            m_bom{};

    //editor variables
    std::u16string  m_curStrBuff;
    size_t          m_curStr{STR_NOTDEFINED};
    bool            m_curChanged{};

    bool    ApplyBuffer(const std::shared_ptr<read_buff_t>& buff, size_t read, size_t& buffOffset,
        std::shared_ptr<StrBuff<std::string, std::string_view>>& strBuff, size_t& strOffset,
        uintmax_t& fileOffset, bool eof);
    bool    FillStrOffset(std::shared_ptr<StrBuff<std::string, std::string_view>> strBuff, size_t size, bool last, size_t& rest);
    bool    ImproveBuff(std::list<std::shared_ptr<StrBuff<std::string, std::string_view>>>::iterator strBuff);

    std::u16string  _GetStr(size_t line, size_t offset, size_t size);
    bool    _AddStr(size_t n, const std::u16string& str);
    bool    AddStr(size_t n, const std::u16string& str);
//    bool    AppendStr(const std::u16string& str);
    bool    ChangeStr(size_t n, const std::u16string& str);
    bool    ConvertStr(const std::u16string& str, std::string& buff) const;

    bool    LoadBuff(uint64_t offset, size_t size, std::shared_ptr<std::string> buff);
    bool    BackupFile();
    bool    Clear();

public:
    Editor(const Editor&) = delete;
    void operator= (const Editor&) = delete;
    
    Editor() = default;
    Editor(const std::filesystem::path& file, const std::string& parseStyle = "", const std::string& cp = "")
        : m_file{file}
    {
        m_lexParser.SetParseStyle(parseStyle);
        m_tab = m_lexParser.GetTabSize();
        m_saveTab = m_lexParser.GetSaveTab();
        m_showTab = m_lexParser.GetShowTab();
        SetCP(cp);
        Clear();
    }

    static size_t UStrLen(const std::u16string& str) 
    {
        auto pos = str.find_last_not_of(' ');
        if (pos == std::string::npos)
        {
            if (!str.empty() && str[0] <= ' ')
                return 0;
            return str.size();
        }
        else
            return pos + 1;
    }

    using progress_func = std::function<bool()>;
    static bool ScanFile(const std::filesystem::path& file, const std::u16string& toFind, const std::string& cp, bool checkCase, bool findWord, progress_func func);

    bool                    SetFilePath(const std::filesystem::path& file);
    std::filesystem::path   GetFilePath() const {return m_file;}

    bool                    LinkWnd(FrameWnd* wnd) { m_wndList.insert(wnd); return true; }
    bool                    UnlinkWnd(FrameWnd* wnd) { m_wndList.erase(wnd); return true; }
    std::list<FrameWnd*>    GetLinkedWnd(FrameWnd* wnd = nullptr) const;
    bool                    InvalidateWnd(size_t line, invalidate_t type, pos_t pos = 0, pos_t size = 0) const;
    bool                    RefreshAllWnd(FrameWnd* wnd) const;

    bool                    Load(bool log = false);
    bool                    LoadTail();
    bool                    Save();
    bool                    SetName(const std::filesystem::path& file, bool copy);
    bool                    ClearModifyFlag();
    char                    GetAccessInfo();
    file_state              CheckFile();
    bool                    IsFileInMemory();

    size_t                  GetMaxStrLen() const    {return m_maxStrlen;}
    void                    SetMaxStrLen(size_t len){m_maxStrlen = std::min(static_cast<size_t>(MAX_STRLEN), len);}
    std::string             GetCP() const           {return m_cp;}
    bool                    SetCP(const std::string& cp);
    eol_t                   GetEol() const          {return m_eol;}
    void                    SetEol(eol_t eol)       {m_eol = eol;}
    size_t                  GetTab() const          {return m_tab;}
    void                    SetTab(size_t tabsize);
    bool                    GetSaveTab() const      {return m_saveTab;}
    void                    SetSaveTab(bool save)   {m_saveTab = save;}
    bool                    GetShowTab() const      {return m_showTab;}
    void                    SetShowTab(bool show)   {m_lexParser.SetShowTab(m_showTab = show);}

    size_t                  GetStrCount() const     {return m_buffer.GetStrCount(); }
    bool                    IsChanged() const       {return m_curChanged || m_buffer.IsChanged(); }
    uint64_t                GetSize() const         {return m_buffer.GetSize(); }
    bool                    SetCurStr(size_t line);
    bool                    FlushCurStr();

    std::u16string          GetStr(size_t line, size_t offset = 0, size_t size = MAX_STRLEN + 1);
    std::u16string          GetStrForFind(size_t line, bool checkCase, bool fast);

    //editor API with undo
    bool                    CorrectTab(bool save, size_t line, std::u16string& str);
    bool                    SaveTab(bool save, size_t line);
    bool                    RestoreTab(bool save, size_t line, const std::u16string& str);

    bool                    AddLine(bool save, size_t line, const std::u16string& str);
    bool                    DelLine(bool save, size_t line, size_t count = 1);
    bool                    MergeLine(bool save, size_t line, size_t pos = MAX_STRLEN + 1, size_t indent = 0);//merge with next
    bool                    SplitLine(bool save, size_t line, size_t pos, size_t indent = 0);

    bool                    AddSubstr(bool save, size_t line, size_t pos, const std::u16string& substr);
    bool                    ChangeSubstr(bool save, size_t line, size_t pos, const std::u16string& substr);
    bool                    ClearSubstr(bool save, size_t line, size_t pos, size_t len);
    bool                    DelSubstr(bool save, size_t line, size_t pos, size_t len);
    bool                    ReplaceSubstr(bool save, size_t line, size_t pos, size_t len, const std::u16string& substr);
    bool                    Indent(bool save, size_t line, size_t pos, size_t len, size_t n);
    bool                    Unindent(bool save, size_t line, size_t pos, size_t len, size_t n);

    bool                    AddCh(bool save, size_t line, size_t pos, char16_t ch);
    bool                    ChangeCh(bool save, size_t line, size_t pos, char16_t ch);
    bool                    DelCh(bool save, size_t line, size_t pos)    {return DelSubstr(save, line, pos, 1);}
    bool                    DelBegin(bool save, size_t line, size_t pos) {return DelSubstr(save, line, 0, pos);}
    bool                    DelEnd(bool save, size_t line, size_t pos)   {return ClearSubstr(save, line, pos, m_maxStrlen - pos);}

    //undo control
    void                    SetUndoRemark(const std::string& rem) {m_undoList.SetRemark(rem);}
    bool                    AddUndoCommand(const EditCmd& editCmd, const EditCmd& undoCmd);
    bool                    Command(const EditCmd& cmd);
    std::optional<EditCmd>  GetUndo() { return m_undoList.GetUndoCmd(); }
    std::optional<EditCmd>  GetRedo() { return m_undoList.GetEditCmd(); }
    std::optional<EditCmd>  PeekUndo() { return m_undoList.PeekUndoCmd(); }
    std::optional<EditCmd>  PeekRedo() { return m_undoList.PeekEditCmd(); }

    //lexical API
    bool                    SetParseStyle(const std::string& style);
    std::string             GetParseStyle() const { return m_lexParser.GetParseStyle(); }
    bool                    GetColor(size_t line, const std::u16string& str, std::vector<color_t>& buff, size_t len);
    bool                    CheckLexPair(size_t& line, size_t& pos);
};

using EditorPtr = std::shared_ptr<Editor>;

} //namespace _Editor


<|start_filename|>Editor/src/Dialogs/FindFileDialog.cpp<|end_filename|>
/*
FreeBSD License

Copyright (c) 2020-2021 vikonix: <EMAIL>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "Dialogs/EditorDialogs.h"
#include "utfcpp/utf8.h"
#include "WndManager/DlgControls.h"
#include "WndManager/App.h"
#include "WndManager/WndManager.h"
#include "utils/CpConverter.h"
#include "EditorWnd.h"
#include "EditorApp.h"

using namespace _Utils;

namespace _Editor 
{

/////////////////////////////////////////////////////////////////////////////
FindFileDialogVars FindFileDialog::s_vars;

#define ID_FF_SEARCH   (ID_USER +  1)
#define ID_FF_REPLACE  (ID_USER +  2)
#define ID_FF_SREPLACE (ID_USER +  3)
#define ID_FF_SMASK    (ID_USER +  4)
#define ID_FF_MASK     (ID_USER +  5)
#define ID_FF_PATH     (ID_USER +  6)
#define ID_FF_FILELIST (ID_USER +  7)
#define ID_FF_DIRLIST  (ID_USER +  8)
#define ID_FF_CASE     (ID_USER +  9)
#define ID_FF_SUBDIR   (ID_USER + 10)
#define ID_FF_OPEN     (ID_USER + 11)
#define ID_FF_PROMPT   (ID_USER + 12)
#define ID_FF_INMARKED (ID_USER + 13)
#define ID_FF_CP       (ID_USER + 14)

std::list<control> findFileDialog 
{
    {CTRL_TITLE,                        "",                             0,              {},                                  1,  0, 70, 21},

    {CTRL_STATIC,                       "&Search for:",                 0,              {},                                  1,  0, 14},
    {CTRL_EDITDROPLIST,                 "",                             ID_FF_SEARCH,   &FindDialog::s_vars.findStr,        15,  0, 52,  7, "Input string for search"},
    {CTRL_STATIC,                       "&Replace with:",               ID_FF_SREPLACE, {},                                  1,  1, 14},
    {CTRL_EDITDROPLIST,                 "",                             ID_FF_REPLACE,  &FindDialog::s_vars.replaceStr,     15,  1, 52,  7, "Input string for replace"},
    {CTRL_STATIC,                       "File &mask:",                  ID_FF_SMASK,    {},                                  1,  3, 14},
    {CTRL_EDITDROPLIST,                 "",                             ID_FF_MASK,     &FileDialog::s_vars.file,           15,  3, 52,  7, "Input file mask for file search"},

    {CTRL_STATIC | CTRL_NOCOLOR,        "",                             ID_FF_PATH,     {},                                  1,  4, 66},
    {CTRL_LIST,                         "&Directories",                 ID_FF_DIRLIST,  {},                                  0,  5, 19, 14, "Select directory"},
    {CTRL_LIST,                         "&Files",                       ID_FF_FILELIST, {},                                 19,  5, 34,  8, "Select file name"},

    {CTRL_DEFBUTTON | CTRL_ALIGN_RIGHT, "",                             ID_OK,          {},                                 54,  6,  0,  0, "Start search process"},
    {CTRL_BUTTON | CTRL_ALIGN_RIGHT,    "Cancel",                       ID_CANCEL,      {},                                 54,  8},
    {CTRL_LINE,                         "",                             0,              {},                                 54, 12, 13},

    {CTRL_CHECK,                        "&Case sensitive",              ID_FF_CASE,     &FindDialog::s_vars.checkCase,      20, 13,  0,  0, "Search case sensitive or not"},
    {CTRL_CHECK,                        "&Whole word",                  3,              &FindDialog::s_vars.findWord,       20, 14,  0,  0, "Search whole word or phrase"},
    {CTRL_CHECK,                        "Search in s&ub-directories",   ID_FF_SUBDIR,   &FindFileDialog::s_vars.recursive,  20, 15,  0,  0, "Recursive search in subdirectories or not"},
    {CTRL_CHECK,                        "Search in &opened files only", ID_FF_OPEN,     &FindFileDialog::s_vars.inOpen,     20, 16,  0,  0, "Search just in all opened files"},
    {CTRL_CHECK,                        "Without &prompt for replace",  ID_FF_PROMPT,   &FindDialog::s_vars.noPrompt,       20, 17,  0,  0, "Replace in all files without prompt"},

    {CTRL_STATIC,                       "&Encoding:",                   0,              {},                                 54, 13, 14},
    {CTRL_DROPLIST,                     "",                             ID_FF_CP,       &FileDialog::s_vars.cp,             54, 14, 13,  6, "Select file encoding"}
};

FindFileDialog::FindFileDialog(bool replace, pos_t x, pos_t y)
    : Dialog(findFileDialog, x, y)
    , m_replace{ replace }
{
    FindDialog::s_vars.replaceMode = replace;
}

bool FindFileDialog::OnActivate()
{
    Wnd* wnd = WndManager::getInstance().GetWnd();
    if (wnd && wnd->GetWndType() == wnd_t::editor)
    {
        auto editor = dynamic_cast<EditorWnd*>(wnd);

        std::u16string curWord;
        bool cur = editor->GetWord(curWord);
        auto word = utf8::utf16to8(curWord);

        auto seach = GetItem(ID_FF_SEARCH);
        auto ctrlSearch = std::dynamic_pointer_cast<CtrlEditDropList>(seach);
        if (ctrlSearch)
        {
            if (cur)
                ctrlSearch->AppendStr(word);
            for (auto& str : FindDialog::s_vars.findList)
                if (str != word)
                    ctrlSearch->AppendStr(str);
        }

        if (m_replace)
        {
            seach = GetItem(ID_FF_REPLACE);
            ctrlSearch = std::dynamic_pointer_cast<CtrlEditDropList>(seach);
            if (ctrlSearch)
            {
                if (cur)
                    ctrlSearch->AppendStr(word);
                for (auto& str : FindDialog::s_vars.replaceList)
                    if (str != word)
                        ctrlSearch->AppendStr(str);
            }
        }
    }

    if (!m_replace)
    {
        GetItem(0)->SetName("Find");
        GetItem(ID_OK)->SetName("Find");

        GetItem(ID_FF_SREPLACE)->SetMode(CTRL_HIDE);
        GetItem(ID_FF_REPLACE)->SetMode(CTRL_HIDE);
        GetItem(ID_FF_PROMPT)->SetMode(CTRL_HIDE);

        pos_t x, y, sizex, sizey;
        auto smask = GetItem(ID_FF_SMASK);
        smask->GetPos(x, y, sizex, sizey);
        smask->SetPos(x, y - 1);

        auto mask = GetItem(ID_FF_MASK);
        mask->GetPos(x, y, sizex, sizey);
        mask->SetPos(x, y - 1);
    }
    else
    {
        GetItem(0)->SetName("Find And Repalece");
        GetItem(ID_OK)->SetName("Replace");
    }

    auto cp = GetItem(ID_FF_CP);
    auto ctrlCp = std::dynamic_pointer_cast<CtrlDropList>(cp);
    if (ctrlCp)
    {
        for (const std::string& str : iconvpp::CpConverter::GetCpList())
            ctrlCp->AppendStr(str);
        ctrlCp->SetSelect(FileDialog::s_vars.cp);
    }

    auto name = GetItem(ID_FF_MASK);
    auto ctrlName = std::dynamic_pointer_cast<CtrlEditDropList>(name);
    if (FileDialog::s_vars.maskList.empty())
        FileDialog::s_vars.maskList.push_front("*.*");
    for (const std::string& str : FileDialog::s_vars.maskList)
        ctrlName->AppendStr(str);
    ctrlName->SetName(FileDialog::s_vars.maskList.front());

    m_dirList.SetMask(FileDialog::s_vars.maskList.front());
    ScanDir(FileDialog::s_vars.path);

    return true;
}

bool FindFileDialog::ScanDir(const std::string& mask)
{
    std::u16string wmask = utf8::utf8to16(mask);
    m_dirList.SetMask(wmask);
    m_dirList.Scan();

    auto path = m_dirList.GetPath() / utf8::utf8to16(m_dirList.GetMask());
    auto pathCtrl = GetItem(ID_FF_PATH);
    pathCtrl->SetName(Directory::CutPath(path, pathCtrl->GetSizeX()));

    auto fList = GetItem(ID_FF_FILELIST);
    auto fListPtr = std::dynamic_pointer_cast<CtrlList>(fList);
    fListPtr->Clear();

    for (auto& file : m_dirList.GetFileList())
        fListPtr->AppendStr(file.path().filename().u8string());

    auto dList = GetItem(ID_FF_DIRLIST);
    auto dListPtr = std::dynamic_pointer_cast<CtrlList>(dList);
    dListPtr->Clear();

    for (auto& dir : m_dirList.GetDirList())
        dListPtr->AppendStr(dir);
    for (auto& drv : m_dirList.GetDrvList())
        dListPtr->AppendStr(drv);

    int item = SelectItem(ID_FF_FILELIST);
    DialogProc(K_SELECT);

    SelectItem(item);
    Refresh();

    return m_dirList.IsFound();
}

input_t FindFileDialog::DialogProc(input_t code)
{
    //LOG(DEBUG) << __FUNC__ << " code=" << std::hex << code << std::dec;

    if ((code & K_TYPEMASK) == K_SYMBOL)
    {
        if (code == K_ENTER)
        {
            if (GetSelectedId() == ID_FF_DIRLIST)
            {
                auto list = GetItem(ID_FF_DIRLIST);
                auto listPtr = std::dynamic_pointer_cast<CtrlList>(list);
                auto item = listPtr->GetSelected();
                auto dir = listPtr->GetStr(item);

                ScanDir(std::string(dir));
                code = 0;
            }
            else if (GetSelectedId() == ID_FF_MASK)
            {
                auto name = GetItem(ID_FF_MASK)->GetName();
                ScanDir(std::string(name));
                code = 0;
                SelectItem(ID_FF_SEARCH);
            }
        }
        else if (code == K_BS)
        {
            if (GetSelectedId() == ID_FF_DIRLIST)
            {
                //LOG(DEBUG) << "Dirlist BS";
                ScanDir("..");
                code = 0;
            }
        }
    }

    return code;
}

bool FindFileDialog::OnClose(int id)
{
    if (id == ID_OK)
    {
        auto seach = GetItem(ID_FF_SEARCH);
        auto ctrlSearch = std::dynamic_pointer_cast<CtrlEditDropList>(seach);
        if (ctrlSearch)
        {
            auto str = ctrlSearch->GetName();
            if (str.empty())
            {
                Application::getInstance().SetErrorLine("Search string empty");
                SelectItem(ID_FF_SEARCH);
                Refresh();
                return false;
            }
            if (str.size() > 2)
                _TRY(FindDialog::s_vars.findList.emplace(str))
                FindDialog::s_vars.findStrW = utf8::utf8to16(str);

            size_t n = ctrlSearch->GetStrCount();
            FindDialog::s_vars.findList.clear();
            for (size_t i = 0; i < n && i < 16; ++i)
                _TRY(FindDialog::s_vars.findList.emplace(ctrlSearch->GetStr(i)))
        }

        if (m_replace)
        {
            seach = GetItem(ID_FF_REPLACE);
            auto ctrlReplace = std::dynamic_pointer_cast<CtrlEditDropList>(seach);
            if (ctrlReplace)
            {
                auto str = ctrlReplace->GetName();
                if (str.empty())
                {
                    Application::getInstance().SetErrorLine("Replace string empty");
                    SelectItem(ID_FF_REPLACE);
                    Refresh();
                    return false;
                }
                if (str.size() > 2)
                    _TRY(FindDialog::s_vars.replaceList.emplace(str))
                    FindDialog::s_vars.replaceStrW = utf8::utf8to16(str);

                size_t n = ctrlReplace->GetStrCount();
                FindDialog::s_vars.replaceList.clear();
                for (size_t i = 0; i < n && i < 16; ++i)
                    _TRY(FindDialog::s_vars.replaceList.emplace(ctrlReplace->GetStr(i)))
            }
        }

        auto path = m_dirList.GetPath();
        auto mask = GetItem(ID_FF_MASK)->GetName();
        LOG(DEBUG) << "path=" << path.u8string() << " mask=" << mask;

        FileDialog::s_vars.path = path.u8string();
        FileDialog::s_vars.cpName = GetItem(ID_FF_CP)->GetName();

        FileDialog::s_vars.maskList.remove(mask);
        FileDialog::s_vars.maskList.push_front(mask);
        if (FileDialog::s_vars.maskList.size() > MAX_MASK_LIST)
            FileDialog::s_vars.maskList.pop_back();

    }
    return true;
}


/////////////////////////////////////////////////////////////////////////////
std::vector<path_t> SearchFileDialog::s_foundList;
size_t SearchFileDialog::s_listPos{};

#define ID_SF_PATH     (ID_USER +  1)
#define ID_SF_FILELIST (ID_USER +  2)
#define ID_SF_PROGRESS (ID_USER +  3)
#define ID_SF_COUNT    (ID_USER +  4)

std::list<control> dlgSearchFile
{
    {CTRL_TITLE,                    "File Search",  0,              {},  1,  0, 70, 21},

    {CTRL_STATIC,                   "In:",          0,              {},  1,  0,  4},
    {CTRL_STATIC,                   "",             ID_SF_PATH,     {},  5,  0, 62},

    {CTRL_LIST,                     "",             ID_SF_FILELIST, {},  0,  1, 68, 17},
    {CTRL_STATIC,                   "",             ID_SF_PROGRESS, {},  1, 18,  1},
    {CTRL_STATIC,                   "",             ID_SF_COUNT,    {},  3, 18, 35},
    {CTRL_BUTTON | CTRL_ALIGN_RIGHT,"Stop",         ID_CANCEL,      {}, 40, 18}
};

SearchFileDialog::SearchFileDialog(pos_t x, pos_t y)
    : Dialog(dlgSearchFile, x, y)
{
}

input_t SearchFileDialog::Activate()
{
    path_t path{ utf8::utf8to16(FileDialog::s_vars.path) };
    LOG(DEBUG) << "SearchFile path=" << path << " mask=" << m_mask << " to find=" << utf8::utf16to8(m_toFind) << " cp=" << m_cp;

    m_activeView = WndManager::getInstance().GetActiveView();

    AllignButtons();
    Show();
    Refresh();
    InputCapture();

    Application::getInstance().SetErrorLine("Wait for files scan. Press any key for cancel.");
    s_foundList.clear();
    s_listPos = 0;
    [[maybe_unused]]auto rc = ScanDir(path / utf8::utf8to16(m_mask));

    Hide();
    WndManager::getInstance().SetActiveView(m_activeView);
    Application::getInstance().SetHelpLine();

    return ID_OK;
}

bool SearchFileDialog::ScanDir(const path_t& path)
{
    //LOG(DEBUG) << __FUNC__ << "path=" << path.u8string();
    
    auto sizex = GetItem(ID_SF_PATH)->GetSizeX();
    std::string dirPath = Directory::CutPath(path / m_mask, sizex);
    dirPath.resize(sizex, ' ');
    GetItem(ID_SF_PATH)->SetName(dirPath);

    _Utils::DirectoryList dirList;

    dirList.SetMask(path);
    dirList.SetMask(m_mask);
    dirList.Scan();
    
    bool putPath{};
    auto fList = GetItem(ID_SF_FILELIST);
    auto fListPtr = std::dynamic_pointer_cast<CtrlList>(fList);

    for (auto& file : dirList.GetFileList())
    {
        if(!ShowProgress())
            return false;

        if (FindFileDialog::s_vars.inOpen)
        {
            auto& app = Application::getInstance();
            auto& editorApp = dynamic_cast<EditorApp&>(app);
            auto wnd = editorApp.GetEditorWnd(file.path());
            if (nullptr == wnd)
                continue;
        }
        
        std::string shortPath = Directory::CutPath(file.path(), sizex);
        shortPath.resize(sizex, ' ');
        GetItem(ID_SF_PATH)->SetName(shortPath);

        bool ret = Editor::ScanFile(file.path(), m_toFind, m_cp, m_checkCase, m_findWord, std::bind(&SearchFileDialog::ShowProgress, this));
        if (ret)
        {
            //found
            LOG(DEBUG) << "found in file=" << file.path().u8string();
            s_foundList.emplace_back(file.path());
            std::string count = "Matched " + std::to_string(s_foundList.size()) + " file(s).";
            GetItem(ID_SF_COUNT)->SetName(count);

            if (!putPath)
            {
                putPath = true;
                fListPtr->AppendStr(file.path().parent_path().u8string());
            }
            fListPtr->AppendStr("  " + file.path().filename().u8string());
            fListPtr->SetSelect(fListPtr->GetStrCount() - 1);
        }
    }

    if (!ShowProgress())
        return false;

    if(m_recursive)
        for (auto& dir : dirList.GetDirList())
        {
            if (dir == "..")
                continue;
            bool rc = ScanDir(dirList.GetPath() / utf8::utf8to16(dir));
            if (!rc)
                return false;
        }

    return true;
}

bool SearchFileDialog::ShowProgress()
{
    if (m_cancel)
        return false;

    char buff[2]{ s_progress[m_pos], 0 };
    if (++m_pos >= s_progress.size())
        m_pos = 0;

    GetItem(ID_SF_PROGRESS)->SetName(buff);

    auto key = CheckInput(1ms);
    if (key)
    {
        m_cancel = true;
        return false;
    }

    return true;
}


/////////////////////////////////////////////////////////////////////////////
path_t MatchedFileDialog::s_path;

#define ID_MF_COUNT    (ID_USER +  1)
#define ID_MF_FILELIST (ID_USER +  2)

std::list<control> dlgMatchedFile 
{
    {CTRL_TITLE,                        "Matched Files",    0,              {},                             1,  0, 70, 21},

    {CTRL_LIST,                         "",                 ID_MF_FILELIST, &SearchFileDialog::s_listPos,   0,  0, 68, 18, "Select file for open"},
    {CTRL_STATIC,                       "",                 ID_MF_COUNT,    {},                             1, 18, 47},

    {CTRL_DEFBUTTON | CTRL_ALIGN_RIGHT, "Open",             ID_OK,          {},                            40, 18,  0,  0, "Open selected file in new windows"},
    {CTRL_BUTTON | CTRL_ALIGN_RIGHT,    "Cancel",           ID_CANCEL,      {},                            50, 18}
};

MatchedFileDialog::MatchedFileDialog(pos_t x, pos_t y)
    : Dialog(dlgMatchedFile, x, y)
{
}

bool MatchedFileDialog::OnActivate()
{
    if(SearchFileDialog::s_foundList.empty())
    {
        BeginPaint();
        Application::getInstance().SetErrorLine("Matched files absent");
        StopPaint();
        return false;
    }

    auto fList = GetItem(ID_MF_FILELIST);
    auto fListPtr = std::dynamic_pointer_cast<CtrlList>(fList);
    path_t curPath;

    for (auto& fullPath : SearchFileDialog::s_foundList)
    {
        auto path = fullPath.parent_path();
        if (curPath != path)
        {
            if(curPath != "")
                fListPtr->AppendStr("");

            curPath = path;
            fListPtr->AppendStr(path.u8string());
        }
        fListPtr->AppendStr("  " + fullPath.filename().u8string());
    }

    fListPtr->SetSelect(SearchFileDialog::s_listPos);
    GetItem(ID_MF_COUNT)->SetName("Found " + std::to_string(SearchFileDialog::s_foundList.size()) + " file(s).");

    return true;
}

bool MatchedFileDialog::OnClose(int id)
{
    if (id == ID_OK)
    {
        auto fList = GetItem(ID_MF_FILELIST);
        auto fListPtr = std::dynamic_pointer_cast<CtrlList>(fList);

        auto pos = fListPtr->GetSelected();
        auto str = fListPtr->GetStr(pos);
        if (str.empty() || str[0] != ' ')
        {
            SelectItem(ID_MF_FILELIST);
            Refresh();
            return false;
        }

        size_t entry{};
        path_t curPath;
        for (auto& fullPath : SearchFileDialog::s_foundList)
        {
            auto path = fullPath.parent_path();
            if (curPath != path)
            {
                if (curPath != "")
                    ++entry;

                curPath = path;
                ++entry;
            }
            if (entry == pos)
            {
                s_path = fullPath;
                break;
            }
            ++entry;
        }

    }

    return true;
}

} //namespace _Editor


<|start_filename|>Editor/src/EditorMenu.cpp<|end_filename|>
/*
FreeBSD License

Copyright (c) 2020-2021 vikonix: <EMAIL>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "EditorApp.h"

namespace _Editor
{

/////////////////////////////////////////////////////////////////////////////
Logo g_logo 
{
  ColorScreen,
  ColorScreen,
  '-',
  -1, 
  -1, 
  {
      "----------  ----",
      "--------- # ----",
      "-------- ## ----",
      "------- ###   --",
      "------ #### # --",
      "----- #### ## --",
      "---- #### ###   ",
      "--- #### #### # ",
      "-- #### #### ## ",
      "- #### #### ### ",
      " #### #### #### "
  }
};

/////////////////////////////////////////////////////////////////////////////
//MAIN access menu
menu_list g_accessMenu
{
    {MENU_ITEM, "&F&1Help"},
    {MENU_ITEM, "&F&2Save"},
    {MENU_ITEM, "&F&3Open"},
    {MENU_ITEM, "&F&4Mark"},
    {MENU_ITEM, "&F&5Copy"},
    {MENU_ITEM, "&F&6Move"},
    {MENU_ITEM, "&F&7Search"},
    {MENU_ITEM, "&F&8Unmark"},
    {MENU_ITEM, "&F&9Menu"},
    {MENU_ITEM, "&F&1&0Exit"}
};

//REPLACE access menu
menu_list g_replaceMenu
{
    {MENU_ITEM, "&Prev"},
    {MENU_ITEM, "&Next"},
    {MENU_ITEM, "&All"},

    {MENU_ITEM, "& "},
    {MENU_ITEM, "&Quit Rep"},
    {MENU_ITEM, "lace"}
};

/////////////////////////////////////////////////////////////////////////////
static menu_list menuFile 
{
    {MENU_ITEM,         "&New",                         K_APP_NEW,                  "Open new empty file"},
    {MENU_ITEM,         "&Open...",                     K_APP_DLG_OPEN,             "Open file in new window"},
    {MENU_ITEM,         "&Reload",                      K_ED(E_CTRL_RELOAD),        "Reload current file"},
    {MENU_ITEM,         "&Close",                       K_ED(E_CTRL_CLOSE),         "Close current file"},
    {MENU_SEPARATOR},
    {MENU_ITEM,         "&Save",                        K_ED(E_CTRL_SAVE),          "Save current file"},
    {MENU_ITEM,         "Save &As...",                  K_ED(E_CTRL_SAVEAS),        "Save file as"},
    {MENU_ITEM,         "Save A&ll",                    K_APP_SAVE_ALL,             "Save all changed files"},
    {MENU_SEPARATOR},
    {MENU_ITEM,         "&Properties...",               K_ED(E_CTRL_PROPERTIES),    "Get file properties"},
    {MENU_SEPARATOR},
//    {MENU_ITEM,         "Ne&w Session...",              K_APP_NEW_SESSION,          "Create new session"},
//    {MENU_ITEM,         "O&pen Session...",             K_APP_OPEN_SESSION,         "Open existing session"},
//    {MENU_SEPARATOR},
    {MENU_ITEM,         "Recent &Files",                K_MENU + 9,                 "Recent files list"},
//    {MENU_ITEM,         "Recent Sess&ions",             K_MENU + 10,                "Recent sessions list"},
    {MENU_SEPARATOR},
    {MENU_ITEM,         "&Exit",                        K_EXIT,                     "Exit from editor"}
};

static menu_list menuEdit
{
    {MENU_ITEM,         "&Undo",                        K_ED(E_EDIT_UNDO),          "Undo the last action"},
    {MENU_ITEM,         "&Redo",                        K_ED(E_EDIT_REDO),          "Redo the previously undone action"},
    {MENU_SEPARATOR},
    {MENU_ITEM,         "Cu&t",                         K_ED(E_EDIT_CB_CUT),        "Cut selected text to clipboard"},
    {MENU_ITEM,         "&Copy",                        K_ED(E_EDIT_CB_COPY),       "Copy selected text to clipboard"},
    {MENU_ITEM,         "&Paste",                       K_ED(E_EDIT_CB_PASTE),      "Paste text from clipboard"},
    {MENU_SEPARATOR},
    {MENU_ITEM,         "Delete &Line",                 K_ED(E_EDIT_DEL_STR),       "Delete current line"},
    {MENU_ITEM,         "Delete &Begin of Line",        K_ED(E_EDIT_DEL_BEGIN),     "Delete begin of line to cursor"},
    {MENU_ITEM,         "Delete &End of Line",          K_ED(E_EDIT_DEL_END),       "Delete end of line from cursor"}
};

static menu_list menuBlock 
{
    {MENU_ITEM,         "&Copy Block",                  K_ED(E_EDIT_BLOCK_COPY),    "Copy selected block"},
    {MENU_ITEM,         "&Move Block",                  K_ED(E_EDIT_BLOCK_MOVE),    "Move selected block"},
    {MENU_SEPARATOR},
    {MENU_ITEM,         "Window Cop&y...",              K_APP_WND_COPY,             "Copy block selected in another window"},
    {MENU_ITEM,         "Window Mo&ve...",              K_APP_WND_MOVE,             "Move block selected in another window"},
    {MENU_SEPARATOR},
    {MENU_ITEM,         "&Indent In Block",             K_ED(E_EDIT_BLOCK_INDENT),  "Indent inside of selected block"},
    {MENU_ITEM,         "&Unindent In Block",           K_ED(E_EDIT_BLOCK_UNINDENT),"Unindent inside of selected block"},
    {MENU_SEPARATOR},
    {MENU_ITEM,         "&Select;Shift+Arrows|Esc,Arrows",K_ED(E_SELECT_BEGIN),     "Begin of stream selection"},
    {MENU_ITEM,         "Select &All",                  K_ED(E_SELECT_ALL),         "Select whole file"},
    {MENU_ITEM,         "Select &Line Mode",            K_ED(E_SELECT_MODE),        "Begin of line selection mode"},
    {MENU_ITEM,         "Select C&olumn Mode",          K_ED(E_SELECT_MODE) | 1,    "Begin of column selection mode"},
    {MENU_ITEM,         "&End Select Mode",             K_ED(E_SELECT_END),         "End of selection mode"},
    {MENU_SEPARATOR},
    {MENU_ITEM,         "U&nselect",                    K_ED(E_SELECT_UNSELECT),    "Hide selection"}
};

static menu_list menuSearch 
{
    {MENU_ITEM,         "&Find...",                     K_ED(E_DLG_FIND),           "Find in current window"},
    {MENU_ITEM,         "&Replace...",                  K_ED(E_DLG_REPLACE),        "Find and replace in current window"},
    {MENU_ITEM,         "Repe&at",                      K_ED(E_CTRL_REPEAT),        "Repeat last find/replace operation"},
    {MENU_ITEM,         "Find Again &Up",               K_ED(E_CTRL_FINDUP),        "Repeat last find up"},
    {MENU_ITEM,         "Find Again &Down",             K_ED(E_CTRL_FINDDN),        "Repeat last find down"},
    {MENU_SEPARATOR},
    {MENU_ITEM,         "Current Word U&p",             K_ED(E_CTRL_FINDUPW),       "Get word under the cursor and find up"},
    {MENU_ITEM,         "Current Word Dow&n",           K_ED(E_CTRL_FINDDNW),       "Get word under the cursor and find down"},
    {MENU_SEPARATOR},
    {MENU_ITEM,         "&Goto Line...",                K_ED(E_DLG_GOTO),           "Go to line number"},
//    {MENU_SEPARATOR},
//    {MENU_ITEM,         "F&ind in Files...",            K_APP_FINDFILE,             "Find in all files"},
//    {MENU_ITEM,         "R&eplace in Files...",         K_APP_REPLACEFILE,          "Find and replace in all files"},
//    {MENU_ITEM,         "Match &List...",               K_APP_FOUNDFILE,            "Get matched files list"}
};

static menu_list menuTools 
{
    {MENU_ITEM,         "Goto &Matched Bracket",        K_ED(E_MOVE_LEX_MATCH),     "Go to opposite bracket"},
//    {MENU_ITEM,         "Fu&nctions List...",           K_ED(E_CTRL_FUNC_LIST),     "Get functions list in file"},
//    {MENU_ITEM,         "Bookmark&s...",                K_APP_BOOKMARK_LIST,        "Set or go to bookmark"},
//    {MENU_ITEM,         "&Compare Files...",            K_APP_DIFF,                 "Compare two files for difference"},
    {MENU_SEPARATOR},
    {MENU_ITEM,         "Start/Stop &Record Macro",     K_APP_RECORD_MACRO,         "Start/Stop recording of editing command"},
    {MENU_ITEM,         "&Play Macro",                  K_APP_PLAY_MACRO,           "Repeat recorded editing commands"},
//    {MENU_SEPARATOR},
//    {MENU_ITEM,         "&Options",                     K_MENU + 6,                 "Configuration menu"}
};

static menu_list menuOptions
{
    {MENU_ITEM,         "&Settings...",                 K_APP_SETTINGS,             "Change the application settings"},
    {MENU_ITEM,         "&Colors...",                   K_APP_COLOR,                "Change the application color scheme"}
};

static menu_list menuWindows
{
    {MENU_ITEM,         "&Close Window",                K_ED(E_CTRL_CLOSE),         "Close current window"},
    {MENU_ITEM,         "Close &All Windows",           K_APP_WND_CLOSEALL,         "Close all windows"},
    {MENU_SEPARATOR},
    {MENU_ITEM,         "Split/Merge &View",            K_APP_VIEW_SPLIT,           "Split/Merge window view"},
    {MENU_ITEM,         "Split &Horizontal/Vertical",   K_APP_VIEW_MODE,            "Change split type horizontal <-> vertical"},
    {MENU_ITEM,         "&Move Split Line",             K_APP_VIEW_SIZE,            "Change split line position"},
    {MENU_ITEM,         "&Change Active View;Alt+Page", K_APP_VIEW_SET,             "Set another view as active"},
    {MENU_SEPARATOR},
    {MENU_ITEM,         "Windows &List...",             K_APP_WND_LIST,             "Open windows list"}
};

static menu_list menuHelp 
{
//    {MENU_ITEM,         "&View Help",                   K_APP_HELP,               "Help documentation"},
//    {MENU_ITEM,         "Keyboard &Map",                K_APP_HELP_KEYMAP,        "Keyboard command description"},
//    {MENU_SEPARATOR},
    {MENU_ITEM,         "&About...",                    K_APP_ABOUT,                "About Application"}
};

menu_list g_menuRecentFiles 
{
    {MENU_ITEM, "", K_APP_FILE_RECENT}
};

menu_list g_menuRecentSessions
{
    {MENU_ITEM, "", K_APP_SESSION_RECENT},
    {MENU_ITEM, "", K_APP_SESSION_RECENT + 1},
    {MENU_ITEM, "", K_APP_SESSION_RECENT + 2},
    {MENU_ITEM, "", K_APP_SESSION_RECENT + 3},
    {MENU_ITEM, "", K_APP_SESSION_RECENT + 4},
    {MENU_ITEM, "", K_APP_SESSION_RECENT + 5},
    {MENU_ITEM, "", K_APP_SESSION_RECENT + 6},
    {MENU_ITEM, "", K_APP_SESSION_RECENT + 7},
    {MENU_ITEM, "", K_APP_SESSION_RECENT + 8},
    {MENU_ITEM, "", K_APP_SESSION_RECENT + 9}
};

menu_list menuMain
{
    {MENU_ITEM,         "&File",     K_MENU + 1},
    {MENU_ITEM,         "&Edit",     K_MENU + 2},
    {MENU_ITEM,         "&Selection",K_MENU + 3},
    {MENU_ITEM,         "Sea&rch",   K_MENU + 4},
    {MENU_ITEM,         "&Tools",    K_MENU + 5},
    {MENU_ITEM,         "&Windows",  K_MENU + 7},
    {MENU_ITEM,         "&Help",     K_MENU + 8}
};

menu_list g_popupMenu
{
    {MENU_ITEM,       "&Main Menu",         K_MENU,                 "Open main menu"},
    {MENU_ITEM,       "&Window List...",    K_APP_WND_LIST,         "Open windows list"},
    {MENU_ITEM,       "&Properties...",     K_ED(E_CTRL_PROPERTIES),"Get file properties"},
    {MENU_SEPARATOR},
    {MENU_ITEM,       "C&ut",               K_ED(E_EDIT_CB_CUT),    "Cut selected text to clipboard"},
    {MENU_ITEM,       "&Copy",              K_ED(E_EDIT_CB_COPY),   "Copy selected text to clipboard"},
    {MENU_ITEM,       "P&aste",             K_ED(E_EDIT_CB_PASTE),  "Paste text from clipboard"}
};

std::vector<menu_list> g_mainMenu
{
    menuMain,            //0 
    menuFile,            //1
    menuEdit,            //2
    menuBlock,           //3
    menuSearch,          //4
    menuTools,           //5
    menuOptions,         //6
    menuWindows,         //7
    menuHelp,            //8
    g_menuRecentFiles,   //9
    g_menuRecentSessions //10
};

const size_t c_recentFilesMenu{ 9 };
const size_t c_recentSessionsMenu{ 10 };

} //namespace _Editor


<|start_filename|>Editor/inc/EditorWnd.h<|end_filename|>
/*
FreeBSD License

Copyright (c) 2020-2021 vikonix: <EMAIL>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once

#include "WndManager/Wnd.h"
#include "Editor.h"
#include "EditorCmd.h"

#include <functional>
#include <unordered_map>

using namespace _WndManager;

namespace _Editor
{

class Diff;
class WndConfig;

class EditorWnd : public FrameWnd
{
    inline static const size_t FileCheckInterval{ 10 };
    inline static const size_t LogFileCheckInterval{ 1 };

    enum select_state : int
    {
        no          = 0,
        begin       = 1,
        end         = 2,
        visible     = 4,
        begin_vis   = 5,
        complete    = 6
    };
    bool IsSelectStarted()  { return (m_selectState & select_state::begin) != 0; }
    bool IsSelectFinished() { return (m_selectState & select_state::end) != 0; }
    bool IsSelectVisible()  { return (m_selectState & select_state::visible) != 0; }
    bool IsSelectComplete() { return m_selectState == select_state::complete; }

    enum class select_t
    {
        stream,
        line,
        column
    };

    enum class select_change
    {
        clear,
        insert_ch,
        delete_ch,
        insert_str,
        delete_str,
        split_str,
        merge_str
    };

    enum class select_line
    {
        begin,
        full,
        end,
        substr
    };
    
    using EditorFunc = std::function<bool(EditorWnd*, input_t)>;
    static std::unordered_map<EditorCmd, std::pair<EditorFunc, select_state>> s_funcMap;

    EditorPtr       m_editor;
    std::shared_ptr<EditorWnd>  m_clonedWnd;

    bool            m_clone{};
    bool            m_close{};    //close window at EventProc return
    bool            m_deleted{};  //file was deleted by external program
    bool            m_saved{};    //file was saved
    bool            m_untitled{true};
    bool            m_readOnly{};
    bool            m_log{};
    std::chrono::time_point<std::chrono::system_clock> m_checkTime{ 
        std::chrono::system_clock::now() + std::chrono::seconds(2)};

    //file position
    size_t          m_xOffset{};
    size_t          m_firstLine{};
    pos_t           m_clientSizeX{};
    pos_t           m_clientSizeY{};

    //select mode variables
    select_t        m_selectType{select_t::stream};
    select_state    m_selectState{select_state::no};    //current selection state
    bool            m_selectKeyShift{};                 //select with shift key pressed
    bool            m_selectMouse{};                    //select by mouse
    //select coord
    size_t          m_beginX{};
    size_t          m_beginY{};
    size_t          m_endX{};
    size_t          m_endY{};

    //search coord
    std::u16string  m_findStr;
    size_t          m_foundX{};
    size_t          m_foundY{};
    size_t          m_foundSize{};
    size_t          m_progress{};
    bool            m_markAllFound{};

    //file position info
    size_t          m_infoStrSize{};

    //invalidate
    bool            m_invalidate{};
    pos_t           m_invBeginX{};
    pos_t           m_invBeginY{};
    pos_t           m_invEndX{};
    pos_t           m_invEndY{};

    bool            m_popupMenu{};

    //lex pair
    int             m_lexX{-1};
    int             m_lexY{-1};

    //diff mode
    std::shared_ptr<Diff>   m_diff;
    int                     m_diffBuff{-1};

    bool    _GotoXY(size_t x, size_t y, bool top = false);
    bool    InvalidateRect(pos_t x = 0, pos_t y = 0, pos_t sizex = 0, pos_t sizey = 0);
    bool    PrintStr(pos_t x, pos_t y, const std::u16string& str, size_t offset, size_t len);
    bool    MarkAllFound(const std::u16string& str, std::vector<color_t>& colorBuff);

    bool    UpdateAccessInfo();
    bool    UpdateNameInfo();
    bool    UpdatePosInfo();
    bool    UpdateProgress(size_t step);
    bool    UpdateLexPair();
    bool    ChangeSelected(select_change type, size_t line = 0, size_t pos = 0, size_t size = 1);
    bool    CorrectSelection();
    bool    InsertStr(const std::u16string& str, size_t y = STR_NOTDEFINED, bool save = true);
    bool    CopySelected(std::vector<std::u16string>& strArray, select_t& selType);
    bool    PasteSelected(const std::vector<std::u16string>& strArray, select_t selType);
    bool    DelSelected();
    bool    Mark(size_t bx, size_t by, size_t ex, size_t ey, color_t color = 0, select_t selectType = select_t::stream);
    bool    IsNormalSelection(size_t bx, size_t by, size_t ex, size_t ey) const;
    bool    HideFound();
    bool    FindWord(const std::u16string& str, size_t& begin, size_t& end);
    bool    GetSelectedPos(size_t line, size_t& begin, size_t& end, select_line& type) const;
    bool    Find(bool silence = false);
    bool    FindUp(bool silence = false);
    bool    FindDown(bool silence = false);
    bool    IsWord(const std::u16string& str, size_t offset, size_t len);
    bool    CheckFileChanging();
    bool    ReplaceSubstr(size_t line, size_t pos, size_t len, const std::u16string& substr);
    bool    TryDeleteSelectedBlock();

public:
    EditorWnd(pos_t left = 0, pos_t top = 0, pos_t sizex = 0, pos_t sizey = 0, int border = BORDER_TITLE)
        : FrameWnd(left, top, sizex, sizey, border) {m_cmdParser.SetCmdMap(g_EditKeyMap);}
    virtual ~EditorWnd() = default;

    bool        SetFileName(const std::filesystem::path& file, bool untitled = false, 
        const std::string& parseMode = LexParser::c_TextType, const std::string& cp = "UTF-8");
    bool        SetEditor(EditorPtr editor);
    EditorPtr   GetEditor() { return m_editor; }
    bool        SelectClear();

    bool        SetDiffMode(std::shared_ptr<Diff> diff = nullptr, int buff = -1)
    {
        m_diff = diff;
        m_diffBuff = buff;
        return true;
    }

    input_t     ParseCommand(input_t cmd);

    virtual bool            Destroy() override;
    virtual input_t         EventProc(input_t code) override;
    virtual bool            Refresh() override;
    virtual bool            Repaint() override;
    virtual bool            Invalidate(size_t line, invalidate_t type, size_t pos = 0, size_t size = 0) override;
    virtual char            GetAccessInfo() const override { return m_editor->GetAccessInfo(); }
    virtual std::string     GetObjectName() const override 
    { 
        return m_editor ? m_editor->GetFilePath().filename().u8string() : ""; 
    }
    virtual bool            IsUsedTimer() const override { return true; }
    virtual bool            IsUsedView() const override { return true; }
    virtual wnd_t           GetWndType() const override { return wnd_t::editor; }
    virtual std::filesystem::path   GetFilePath() const override { return m_editor->GetFilePath(); }
    virtual Wnd*            CloneWnd() override;
    virtual bool            IsClone() const override { return m_clone; }

    bool    IsMarked()      { return IsSelectComplete(); }
    bool    IsChanged()     { return m_editor->IsChanged(); }
    bool    IsRO()          { return m_readOnly; }
    bool    SetRO(bool ro)  { return m_readOnly = ro; }
    bool    IsLog()         { return m_log; }
    bool    SetLog(bool log){ return m_log = log; }

    bool    EditWndCopy(EditorWnd* from);
    bool    EditWndMove(EditorWnd* from);
    bool    GetWord(std::u16string& buff);
    bool    GetSelectedLines(size_t& begin, size_t& end);

    bool    SaveCfg(WndConfig& config);
    bool    LoadCfg(const WndConfig& config);

/*
  virtual Wnd*          GetLinkWnd() override   {return m_pTBuff->GetLinkWnd(this);}
  int       IsUntitled()             {return m_fUntitled;}
  long long GetSize()                {return m_pTBuff->GetSize();}

  int       SelectAndGoto(int nline, int size);
  int       ScrollWnd(int iCode, int size = 1);
  size_t    GetCurPos()  {return m_nXOffset + m_cursorx;}
  size_t    GetCurLine() {return m_nFirstLine + m_cursory;}
*/
    ///////////////////////////////////////////////////////////////////////////
    //editor functions
    bool MoveLeft(input_t cmd);
    bool MoveRight(input_t cmd);
    bool MoveScrollLeft(input_t cmd);
    bool MoveScrollRight(input_t cmd);
    bool MoveUp(input_t cmd);
    bool MoveDown(input_t cmd);
    bool MovePageUp(input_t cmd);
    bool MovePageDown(input_t cmd);
    bool MoveFileBegin(input_t cmd);
    bool MoveFileEnd(input_t cmd);
    bool MoveStrBegin(input_t cmd);
    bool MoveStrEnd(input_t cmd);
    bool MoveTabLeft(input_t cmd);
    bool MoveTabRight(input_t cmd);
    bool MoveWordLeft(input_t cmd);
    bool MoveWordRight(input_t cmd);
    bool MovePos(input_t cmd);
    bool MoveCenter(input_t cmd);

    bool SelectWord(input_t cmd);
    bool SelectLine(input_t cmd);
    bool SelectAll(input_t cmd);
    bool SelectBegin(input_t cmd);
    bool SelectEnd(input_t cmd);
    bool SelectUnselect(input_t cmd);
    bool SelectMode(input_t cmd);
    bool SelectAllFound(input_t cmd);

    bool EditC(input_t cmd);
    bool EditDelC(input_t cmd);
    bool EditBS(input_t cmd);
    bool EditTab(input_t cmd);
    bool EditEnter(input_t cmd);
    bool EditDelStr(input_t cmd);
    bool EditDelBegin(input_t cmd);
    bool EditDelEnd(input_t cmd);

    bool EditBlockCopy(input_t cmd);
    bool EditBlockMove(input_t cmd);
    bool EditBlockDel(input_t cmd);
    bool EditBlockIndent(input_t cmd);
    bool EditBlockUnindent(input_t cmd);
    bool EditCopyToClipboard(input_t cmd);
    bool EditCutToClipboard(input_t cmd);
    bool EditPasteFromClipboard(input_t cmd);
    bool EditUndo(input_t cmd);
    bool EditRedo(input_t cmd);

    bool CtrlFind(input_t cmd);
    bool CtrlFindUp(input_t cmd);
    bool CtrlFindDown(input_t cmd);
    bool FindUpWord(input_t cmd);
    bool FindDownWord(input_t cmd);
    bool Replace(input_t cmd);
    bool Repeat(input_t cmd);

    bool DlgGoto(input_t cmd);
    bool DlgFind(input_t cmd);
    bool DlgReplace(input_t cmd);

    bool CtrlRefresh(input_t cmd);
    bool Reload(input_t cmd);
    bool Save(input_t cmd);
    bool SaveAs(input_t cmd);
    bool Close(input_t cmd);

    bool MoveLexMatch(input_t cmd);
    bool CtrlFuncList(input_t cmd);
    bool CtrlProperties(input_t cmd);
    bool TrackPopupMenu(input_t cmd);
};

} //namespace _Editor
