terminal_input_parser.cpp
ftxui/component/terminal_input_parser.hpp
cstdint
ftxui/component/mouse.hpp
ftxui/component/receiver.hpp
map
memory
utility
vector
ftxui/component/event.hpp
ftxui/component/task.hpp
ftxui/component/event.hpp
ftxui/component/mouse.hpp
ftxui/component/receiver.hpp
ftxui/component/task.hpp
src/ftxui/component/terminal_input_parser.cpp
ftxui/component/terminal_input_parser.hpp
algorithm
atomic
condition_variable
cstdint
functional
map
memory
mutex
queue
string
utility
variant
vector
ftxui

    //Copyright2020ArthurSonzogni.Allrightsreserved.
    //UseofthissourcecodeisgovernedbytheMITlicensethatcanbefoundin
    //theLICENSEfile.
    #include"ftxui/component/terminal_input_parser.hpp"

    #include<cstdint>//foruint32_t
    #include<ftxui/component/mouse.hpp>//forMouse,Mouse::Button,Mouse::Motion
    #include<ftxui/component/receiver.hpp>//forSenderImpl,Sender
    #include<map>
    #include<memory>//forunique_ptr,allocator
    #include<utility>//formove
    #include<vector>
    #include"ftxui/component/event.hpp"//forEvent
    #include"ftxui/component/task.hpp"//forTask

    namespaceftxui{

    //NOLINTNEXTLINE
    conststd::map<std::string,std::string>g_uniformize={
    //Microsoft'sterminalusesadifferentnewlinecharacterforthereturn
    //key.Thisalsohappenswithlinuxwiththe`bind`command:
    //Seehttps://github.com/ArthurSonzogni/FTXUI/issues/337
    //Here,weuniformizethenewlinecharacterto`\n`.
    {"\r","\n"},

    //See:https://github.com/ArthurSonzogni/FTXUI/issues/508
    {std::string({8}),std::string({127})},

    //See:https://github.com/ArthurSonzogni/FTXUI/issues/626
    //
    //DependingontheCursorKeyMode(DECCKM),theterminalsendsdifferent
    //escapesequences:
    //
    //KeyNormalApplication
    //------------------------
    //UpESC[AESCOA
    //DownESC[BESCOB
    //RightESC[CESCOC
    //LeftESC[DESCOD
    //HomeESC[HESCOH
    //EndESC[FESCOF
    //
    {"\x1BOA","\x1B[A"},//UP
    {"\x1BOB","\x1B[B"},//DOWN
    {"\x1BOC","\x1B[C"},//RIGHT
    {"\x1BOD","\x1B[D"},//LEFT
    {"\x1BOH","\x1B[H"},//HOME
    {"\x1BOF","\x1B[F"},//END

    //VariationsaroundtheFNkeys.
    //Internally,weareusing:
    //vt220,xterm-vt200,xterm-xf86-v44,xterm-new,mgt,screen
    //See:https://invisible-island.net/xterm/xterm-function-keys.html

    //ForlinuxOSconsole(CTRL+ALT+FN),whodonotbelongtoany
    //realstandard.
    //See:https://github.com/ArthurSonzogni/FTXUI/issues/685
    {"\x1B[[A","\x1BOP"},//F1
    {"\x1B[[B","\x1BOQ"},//F2
    {"\x1B[[C","\x1BOR"},//F3
    {"\x1B[[D","\x1BOS"},//F4
    {"\x1B[[E","\x1B[15~"},//F5

    //xterm-r5,xterm-r6,rxvt
    {"\x1B[11~","\x1BOP"},//F1
    {"\x1B[12~","\x1BOQ"},//F2
    {"\x1B[13~","\x1BOR"},//F3
    {"\x1B[14~","\x1BOS"},//F4

    //vt100
    {"\x1BOt","\x1B[15~"},//F5
    {"\x1BOu","\x1B[17~"},//F6
    {"\x1BOv","\x1B[18~"},//F7
    {"\x1BOl","\x1B[19~"},//F8
    {"\x1BOw","\x1B[20~"},//F9
    {"\x1BOx","\x1B[21~"},//F10

    //scoansi
    {"\x1B[M","\x1BOP"},//F1
    {"\x1B[N","\x1BOQ"},//F2
    {"\x1B[O","\x1BOR"},//F3
    {"\x1B[P","\x1BOS"},//F4
    {"\x1B[Q","\x1B[15~"},//F5
    {"\x1B[R","\x1B[17~"},//F6
    {"\x1B[S","\x1B[18~"},//F7
    {"\x1B[T","\x1B[19~"},//F8
    {"\x1B[U","\x1B[20~"},//F9
    {"\x1B[V","\x1B[21~"},//F10
    {"\x1B[W","\x1B[23~"},//F11
    {"\x1B[X","\x1B[24~"},//F12
    };

    TerminalInputParser::TerminalInputParser(Sender<Task>out)
    :out_(std::move(out)){}

    voidTerminalInputParser::Timeout(inttime){
    timeout_+=time;
    constinttimeout_threshold=50;
    if(timeout_<timeout_threshold){
    return;
    }
    timeout_=0;
    if(!pending_.empty()){
    Send(SPECIAL);
    }
    }

    voidTerminalInputParser::Add(charc){
    pending_+=c;
    timeout_=0;
    position_=-1;
    Send(Parse());
    }

    unsignedcharTerminalInputParser::Current(){
    returnpending_[position_];
    }

    boolTerminalInputParser::Eat(){
    position_++;
    returnposition_<static_cast<int>(pending_.size());
    }

    voidTerminalInputParser::Send(TerminalInputParser::Outputoutput){
    switch(output.type){
    caseUNCOMPLETED:
    return;

    caseDROP:
    pending_.clear();
    return;

    caseCHARACTER:
    out_->Send(Event::Character(std::move(pending_)));
    pending_.clear();
    return;

    caseSPECIAL:{
    autoit=g_uniformize.find(pending_);
    if(it!=g_uniformize.end()){
    pending_=it->second;
    }
    out_->Send(Event::Special(std::move(pending_)));
    pending_.clear();
    }
    return;

    caseMOUSE:
    out_->Send(Event::Mouse(std::move(pending_),output.mouse));//NOLINT
    pending_.clear();
    return;

    caseCURSOR_POSITION:
    out_->Send(Event::CursorPosition(std::move(pending_),//NOLINT
    output.cursor.x,//NOLINT
    output.cursor.y));//NOLINT
    pending_.clear();
    return;

    caseCURSOR_SHAPE:
    out_->Send(Event::CursorShape(std::move(pending_),output.cursor_shape));
    pending_.clear();
    return;
    }
    //NOT_REACHED().
    }

    TerminalInputParser::OutputTerminalInputParser::Parse(){
    if(!Eat()){
    returnUNCOMPLETED;
    }

    if(Current()=='\x1B'){
    returnParseESC();
    }

    if(Current()<32){//C0NOLINT
    returnSPECIAL;
    }

    if(Current()==127){//Delete//NOLINT
    returnSPECIAL;
    }

    returnParseUTF8();
    }

    //Codepoint<->UTF-8conversion
    //
    //┏━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓
    //┃Byte1┃Byte2┃Byte3┃Byte4┃
    //┡━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━┩
    //│0xxxxxxx││││
    //├────────┼────────┼────────┼────────┤
    //│110xxxxx│10xxxxxx│││
    //├────────┼────────┼────────┼────────┤
    //│1110xxxx│10xxxxxx│10xxxxxx││
    //├────────┼────────┼────────┼────────┤
    //│11110xxx│10xxxxxx│10xxxxxx│10xxxxxx│
    //└────────┴────────┴────────┴────────┘
    //
    //Thensomesequencesareillegalifitexistashorterrepresentationofthe
    //samecodepoint.
    TerminalInputParser::OutputTerminalInputParser::ParseUTF8(){
    autohead=Current();
    unsignedcharselector=0b1000'0000;//NOLINT

    //Thenoncode-pointpartofthefirstbyte.
    unsignedcharmask=selector;

    //Findthefirstzerointhefirstbyte.
    unsignedintfirst_zero=8;//NOLINT
    for(unsignedinti=0;i<8;++i){//NOLINT
    mask|=selector;
    if(!(head&selector)){
    first_zero=i;
    break;
    }
    selector>>=1U;
    }

    //Accumulatethevalueofthefirstbyte.
    autovalue=uint32_t(head&~mask);//NOLINT

    //InvalidUTF8,withmorethan5bytes.
    constunsignedintmax_utf8_bytes=5;
    if(first_zero==1||first_zero>=max_utf8_bytes){
    returnDROP;
    }

    //MultibyteUTF-8.
    for(unsignedinti=2;i<=first_zero;++i){
    if(!Eat()){
    returnUNCOMPLETED;
    }

    //Invalidcontinuationbyte.
    head=Current();
    if((head&0b1100'0000)!=0b1000'0000){//NOLINT
    returnDROP;
    }
    value<<=6;//NOLINT
    value+=head&0b0011'1111;//NOLINT
    }

    //CheckforoverlongUTF8encoding.
    intextra_byte=0;
    if(value<=0b000'0000'0111'1111){//NOLINT
    extra_byte=0;//NOLINT
    }elseif(value<=0b000'0111'1111'1111){//NOLINT
    extra_byte=1;//NOLINT
    }elseif(value<=0b1111'1111'1111'1111){//NOLINT
    extra_byte=2;//NOLINT
    }elseif(value<=0b1'0000'1111'1111'1111'1111){//NOLINT
    extra_byte=3;//NOLINT
    }else{//NOLINT
    returnDROP;
    }

    if(extra_byte!=position_){
    returnDROP;
    }

    returnCHARACTER;
    }

    TerminalInputParser::OutputTerminalInputParser::ParseESC(){
    if(!Eat()){
    returnUNCOMPLETED;
    }
    switch(Current()){
    case'P':
    returnParseDCS();
    case'[':
    returnParseCSI();
    case']':
    returnParseOSC();

    //Expecting2characters.
    case'':
    case'#':
    case'%':
    case'(':
    case')':
    case'*':
    case'+':
    case'O':
    case'N':{
    if(!Eat()){
    returnUNCOMPLETED;
    }
    returnSPECIAL;
    }
    //Expecting1character:
    default:
    returnSPECIAL;
    }
    }

    //ESCP...ESCBACKSLASH
    TerminalInputParser::OutputTerminalInputParser::ParseDCS(){
    //ParseuntilthestringterminatorST.
    while(true){
    if(!Eat()){
    returnUNCOMPLETED;
    }

    if(Current()!='\x1B'){
    continue;
    }

    if(!Eat()){
    returnUNCOMPLETED;
    }

    if(Current()!='\\'){
    continue;
    }

    if(pending_.size()==10&&//
    pending_[2]=='1'&&//
    pending_[3]=='$'&&//
    pending_[4]=='r'&&//
    true){
    Outputoutput(CURSOR_SHAPE);
    output.cursor_shape=pending_[5]-'0';
    returnoutput;
    }

    returnSPECIAL;
    }
    }

    TerminalInputParser::OutputTerminalInputParser::ParseCSI(){
    boolaltered=false;
    intargument=0;
    std::vector<int>arguments;
    while(true){
    if(!Eat()){
    returnUNCOMPLETED;
    }

    if(Current()=='<'){
    altered=true;
    continue;
    }

    if(Current()>='0'&&Current()<='9'){
    argument*=10;//NOLINT
    argument+=Current()-'0';
    continue;
    }

    if(Current()==';'){
    arguments.push_back(argument);
    argument=0;
    continue;
    }

    //CSIisterminatedbyacharacterintherange0x40–0x7E
    //(ASCII@A–Z[\]^_`a–z{|}~),
    if(Current()>='@'&&Current()<='~'&&
    //Note:Idon'trememberwhyweexclude'<'
    Current()!='<'&&
    //TohandleF1-F4,weexclude'['.
    Current()!='['){
    arguments.push_back(argument);
    argument=0;//NOLINT

    switch(Current()){
    case'M':
    returnParseMouse(altered,true,std::move(arguments));
    case'm':
    returnParseMouse(altered,false,std::move(arguments));
    case'R':
    returnParseCursorPosition(std::move(arguments));
    default:
    returnSPECIAL;
    }
    }

    //InvalidESCinCSI.
    if(Current()=='\x1B'){
    returnSPECIAL;
    }
    }
    }

    TerminalInputParser::OutputTerminalInputParser::ParseOSC(){
    //ParseuntilthestringterminatorST.
    while(true){
    if(!Eat()){
    returnUNCOMPLETED;
    }
    if(Current()!='\x1B'){
    continue;
    }
    if(!Eat()){
    returnUNCOMPLETED;
    }
    if(Current()!='\\'){
    continue;
    }
    returnSPECIAL;
    }
    }

    TerminalInputParser::OutputTerminalInputParser::ParseMouse(//NOLINT
    boolaltered,
    boolpressed,
    std::vector<int>arguments){
    if(arguments.size()!=3){
    returnSPECIAL;
    }

    (void)altered;

    Outputoutput(MOUSE);
    output.mouse.motion=Mouse::Motion(pressed);//NOLINT

    //BitsvalueModiferComment
    //-------------------------
    //0112button0=Left,1=Middle,2=Right,3=Release
    //24Shift
    //38Meta
    //416Control
    //532Move
    //664Wheel

    //clang-formatoff
    constintbutton=arguments[0]&(1+2);//NOLINT
    constboolis_shift=arguments[0]&4;//NOLINT
    constboolis_meta=arguments[0]&8;//NOLINT
    constboolis_control=arguments[0]&16;//NOLINT
    constboolis_move=arguments[0]&32;//NOLINT
    constboolis_wheel=arguments[0]&64;//NOLINT
    //clang-formaton

    output.mouse.motion=is_move?Mouse::Moved:Mouse::Motion(pressed);
    output.mouse.button=is_wheel?Mouse::Button(Mouse::WheelUp+button)//
    :Mouse::Button(button);
    output.mouse.shift=is_shift;
    output.mouse.meta=is_meta;
    output.mouse.control=is_control;
    output.mouse.x=arguments[1];//NOLINT
    output.mouse.y=arguments[2];//NOLINT

    //Motionevent.
    returnoutput;
    }

    //NOLINTNEXTLINE
    TerminalInputParser::OutputTerminalInputParser::ParseCursorPosition(
    std::vector<int>arguments){
    if(arguments.size()!=2){
    returnSPECIAL;
    }
    Outputoutput(CURSOR_POSITION);
    output.cursor.y=arguments[0];//NOLINT
    output.cursor.x=arguments[1];//NOLINT
    returnoutput;
    }

    }//namespaceftxui