LCOV - code coverage report
Current view: top level - engine/interface - console.cpp (source / functions) Hit Total Coverage
Test: Libprimis Test Coverage Lines: 199 673 29.6 %
Date: 2025-01-07 07:51:37 Functions: 42 75 56.0 %

          Line data    Source code
       1             : // console.cpp: the console buffer, its display, and command line control
       2             : 
       3             : #include "../libprimis-headers/cube.h"
       4             : #include "../../shared/stream.h"
       5             : 
       6             : #include "console.h"
       7             : #include "control.h"
       8             : #include "cs.h"
       9             : #include "ui.h"
      10             : #include "menus.h"
      11             : 
      12             : //input.h needs rendertext's objects
      13             : #include "render/rendertext.h"
      14             : #include "render/renderttf.h"
      15             : #include "input.h"
      16             : 
      17             : #include "world/octaedit.h"
      18             : 
      19             : int commandmillis = -1;
      20             : 
      21             : struct FilesKey
      22             : {
      23             :     const int type;
      24             :     const std::string dir,
      25             :                       ext;
      26             : 
      27           0 :     FilesKey(int type, const std::string &dir, const std::string &ext) : type(type), dir(dir), ext(ext) {}
      28             : 
      29           0 :     bool operator==(const FilesKey &y) const
      30             :     {
      31           0 :         return type == y.type && dir == y.dir && ext == y.ext;
      32             :     }
      33             : };
      34             : 
      35             : template<>
      36             : struct std::hash<FilesKey>
      37             : {
      38           0 :     size_t operator()(const FilesKey &key) const
      39             :     {
      40           0 :         size_t h = 5381;
      41           0 :         for(int i = 0, k; (k = key.dir[i]); i++)
      42             :         {
      43           0 :             h = ((h<<5)+h)^k;    // bernstein k=33 xor
      44             :         }
      45           0 :         return h;
      46             :     }
      47             : };
      48             : 
      49             : class CompletionFinder
      50             : {
      51             :     public:
      52             :         enum
      53             :         {
      54             :             Files_Directory = 0,
      55             :             Files_List,
      56             :         };
      57             : 
      58             :         void resetcomplete();
      59             :         void addfilecomplete(const char *command, char *dir, char *ext);
      60             :         void addlistcomplete(const char *command, char *list);
      61             : 
      62             :         void complete(char *s, size_t maxlen, const char *cmdprefix);
      63             : 
      64             :         //print to a stream f the listcompletions in the completions filesval
      65             :         void writecompletions(std::fstream& f) const;
      66             : 
      67             :     private:
      68             : 
      69             :         struct FilesVal
      70             :         {
      71             :             public:
      72             :                 int type;
      73             :                 std::string dir,
      74             :                             ext;
      75             :                 std::vector<char *> files;
      76             : 
      77             :                 FilesVal(int type, std::string dir, std::string ext);
      78             :                 ~FilesVal();
      79             : 
      80             :                 void update();
      81             : 
      82             :             private:
      83             :                 int millis;
      84             :         };
      85             : 
      86             :         friend std::hash<FilesKey>;
      87             : 
      88             :         std::unordered_map<FilesKey, FilesVal *> completefiles;
      89             :         std::unordered_map<const char *, FilesVal *> completions;
      90             : 
      91             :         int completesize = 0;
      92             :         char *lastcomplete = nullptr;
      93             : 
      94             :         void addcomplete(const char *command, int type, char *dir, char *ext);
      95             : 
      96             :         char *prependstring(char *d, const char *s, size_t len) const;
      97             : };
      98             : 
      99           0 : CompletionFinder::FilesVal::FilesVal(int type, std::string dir, std::string ext) : type(type), dir(dir), ext(ext[0] ? std::string(ext) : ""), millis(-1)
     100             : {
     101           0 : }
     102             : 
     103           0 : CompletionFinder::FilesVal::~FilesVal()
     104             : {
     105           0 :     for(char* i : files)
     106             :     {
     107           0 :         delete[] i;
     108             :     }
     109           0 : }
     110             : 
     111           0 : void CompletionFinder::FilesVal::update()
     112             : {
     113           0 :     if(type!=Files_Directory || millis >= commandmillis)
     114             :     {
     115           0 :         return;
     116             :     }
     117             :     //first delete old cached file vector
     118           0 :     for(char* i : files)
     119             :     {
     120           0 :         delete[] i;
     121             :     }
     122             :     //generate new one
     123           0 :     listfiles(dir.c_str(), ext.c_str(), files);
     124           0 :     std::sort(files.begin(), files.end());
     125           0 :     for(uint i = 0; i < files.size(); i++)
     126             :     {
     127           0 :         if(i && !std::strcmp(files[i], files[i-1]))
     128             :         {
     129           0 :             delete[] files.at(i);
     130           0 :             files.erase(files.begin() + i);
     131           0 :             i--; //we need to make up for the element we destroyed
     132             :         }
     133             :     }
     134           0 :     millis = totalmillis;
     135             : }
     136             : 
     137           0 : void CompletionFinder::resetcomplete()
     138             : {
     139           0 :     completesize = 0;
     140           0 : }
     141             : 
     142           1 : void CompletionFinder::addfilecomplete(const char *command, char *dir, char *ext)
     143             : {
     144           1 :     addcomplete(command, Files_Directory, dir, ext);
     145           1 : }
     146             : 
     147           1 : void CompletionFinder::addlistcomplete(const char *command, char *list)
     148             : {
     149           1 :     addcomplete(command, Files_List, list, nullptr);
     150           1 : }
     151             : 
     152           0 : void CompletionFinder::complete(char *s, size_t maxlen, const char *cmdprefix)
     153             : {
     154           0 :     size_t cmdlen = 0;
     155           0 :     if(cmdprefix)
     156             :     {
     157           0 :         cmdlen = std::strlen(cmdprefix);
     158           0 :         if(std::strncmp(s, cmdprefix, cmdlen))
     159             :         {
     160           0 :             prependstring(s, cmdprefix, maxlen);
     161             :         }
     162             :     }
     163           0 :     if(!s[cmdlen])
     164             :     {
     165           0 :         return;
     166             :     }
     167           0 :     if(!completesize)
     168             :     {
     169           0 :         completesize = static_cast<int>(std::strlen(&s[cmdlen]));
     170           0 :         delete[] lastcomplete;
     171           0 :         lastcomplete = nullptr;
     172             :     }
     173           0 :     FilesVal *f = nullptr;
     174           0 :     if(completesize)
     175             :     {
     176           0 :         char *end = std::strchr(&s[cmdlen], ' ');
     177           0 :         if(end)
     178             :         {
     179           0 :             f = completions[stringslice(&s[cmdlen], end).str];
     180             :         }
     181             :     }
     182           0 :     const char *nextcomplete = nullptr;
     183           0 :     if(f) // complete using filenames
     184             :     {
     185           0 :         int commandsize = std::strchr(&s[cmdlen], ' ')+1-s;
     186           0 :         f->update();
     187           0 :         for(const char * i : f->files)
     188             :         {
     189           0 :             if(std::strncmp(i, &s[commandsize], completesize+cmdlen-commandsize)==0 &&
     190           0 :                       (!lastcomplete || std::strcmp(i, lastcomplete) > 0) &&
     191           0 :                       (!nextcomplete || std::strcmp(i, nextcomplete) < 0))
     192             :             {
     193           0 :                 nextcomplete = i;
     194             :             }
     195             :         }
     196           0 :         cmdprefix = s;
     197           0 :         cmdlen = commandsize;
     198             :     }
     199             :     else // complete using command or var (ident) names
     200             :     {
     201           0 :         for(auto& [k, id] : idents)
     202             :         {
     203           0 :             if(std::strncmp(id.name, &s[cmdlen], completesize)==0 &&
     204           0 :                       (!lastcomplete || std::strcmp(id.name, lastcomplete) > 0) &&
     205           0 :                       (!nextcomplete || std::strcmp(id.name, nextcomplete) < 0))
     206             :             {
     207           0 :                 nextcomplete = id.name;
     208             :             }
     209             :         }
     210             :     }
     211             : 
     212           0 :     delete[] lastcomplete;
     213           0 :     lastcomplete = nullptr;
     214           0 :     if(nextcomplete)
     215             :     {
     216           0 :         cmdlen = std::min(cmdlen, maxlen-1);
     217           0 :         if(cmdlen)
     218             :         {
     219           0 :             std::memmove(s, cmdprefix, cmdlen);
     220             :         }
     221           0 :         copystring(&s[cmdlen], nextcomplete, maxlen-cmdlen);
     222           0 :         lastcomplete = newstring(nextcomplete);
     223             :     }
     224             : }
     225             : 
     226             : //print to a stream f the listcompletions in the completions filesval
     227           0 : void CompletionFinder::writecompletions(std::fstream& f) const
     228             : {
     229           0 :     std::vector<std::string> cmds;
     230           0 :     for(auto &[k, v] : completions)
     231             :     {
     232           0 :         if(v)
     233             :         {
     234           0 :             cmds.push_back(k);
     235             :         }
     236             :     }
     237           0 :     std::sort(cmds.begin(), cmds.end());
     238           0 :     for(std::string &k : cmds)
     239             :     {
     240           0 :         auto itr = completions.find(k.c_str());
     241           0 :         if(itr == completions.end())
     242             :         {
     243           0 :             conoutf("could not write completion");
     244           0 :             return;
     245             :         }
     246           0 :         const FilesVal *v = (*itr).second;
     247           0 :         if(v->type==Files_List)
     248             :         {
     249           0 :             if(validateblock(v->dir.c_str()))
     250             :             {
     251           0 :                 f << "listcomplete " << escapeid(k.c_str()) << " [" << v->dir << "]\n";
     252             :             }
     253             :             else
     254             :             {
     255           0 :                 f << "listcomplete " << escapeid(k.c_str()) << " " << escapestring(v->dir.c_str()) << std::endl;
     256             :             }
     257             :         }
     258             :         else
     259             :         {
     260           0 :             f << "complete " << escapeid(k.c_str()) << " " << escapestring(v->dir.c_str()) << " " << escapestring(v->ext.size() ? v->ext.c_str() : "*") << std::endl;
     261             :         }
     262             :     }
     263           0 : }
     264             : 
     265           2 : void CompletionFinder::addcomplete(const char *command, int type, char *dir, char *ext)
     266             : {
     267           2 :     if(identflags&Idf_Overridden)
     268             :     {
     269           0 :         conoutf(Console_Error, "cannot override complete %s", command);
     270           2 :         return;
     271             :     }
     272           2 :     if(!dir[0])
     273             :     {
     274           2 :         auto hasfilesitr = completions.find(command);
     275           2 :         if(hasfilesitr != completions.end())
     276             :         {
     277           0 :             (*hasfilesitr).second = nullptr;
     278             :         }
     279           2 :         return;
     280             :     }
     281           0 :     if(type==Files_Directory)
     282             :     {
     283           0 :         int dirlen = static_cast<int>(std::strlen(dir));
     284           0 :         while(dirlen > 0 && (dir[dirlen-1] == '/' || dir[dirlen-1] == '\\'))
     285             :         {
     286           0 :             dir[--dirlen] = '\0';
     287             :         }
     288           0 :         if(ext)
     289             :         {
     290           0 :             if(std::strchr(ext, '*'))
     291             :             {
     292           0 :                 ext[0] = '\0';
     293             :             }
     294           0 :             if(!ext[0])
     295             :             {
     296           0 :                 ext = nullptr;
     297             :             }
     298             :         }
     299             :     }
     300           0 :     FilesKey key(type, dir ? dir : "", dir ? dir : "");
     301           0 :     auto itr = completefiles.find(key);
     302           0 :     if(itr == completefiles.end())
     303             :     {
     304           0 :         FilesVal *f = new FilesVal(type, dir ? dir : "", ext ? ext : "");
     305           0 :         if(type==Files_List)
     306             :         {
     307           0 :             explodelist(dir, f->files);
     308             :         }
     309           0 :         FilesKey newfile = FilesKey(type, f->dir, f->ext);
     310           0 :         itr = completefiles.insert(std::pair<FilesKey, FilesVal *>(newfile, f)).first;
     311           0 :     }
     312           0 :     auto hasfilesitr = completions.find(std::string(command).c_str());
     313           0 :     if(hasfilesitr != completions.end())
     314             :     {
     315           0 :         (*hasfilesitr).second = (*itr).second;
     316             :     }
     317             :     else
     318             :     {
     319           0 :         FilesVal *v = (*itr).second;
     320           0 :         completions[newstring(command)] = v;
     321             :     }
     322           0 : }
     323             : 
     324           0 : char *CompletionFinder::prependstring(char *d, const char *s, size_t len) const
     325             : {
     326           0 :     size_t slen = std::min(std::strlen(s), len);
     327           0 :     std::memmove(&d[slen], d, std::min(len - slen, std::strlen(d) + 1));
     328           0 :     std::memcpy(d, s, slen);
     329           0 :     d[len-1] = 0;
     330           0 :     return d;
     331             : }
     332             : 
     333             : //internally relevant functionality
     334             : namespace
     335             : {
     336             :     constexpr int maxconsolelines = 1000;  //maximum length of conlines reverse queue
     337             : 
     338             :     struct cline
     339             :     {
     340             :         char *line;   //text contents of the line
     341             :         int type,     //one of the enum values Console_* in headers/consts.h
     342             :             outtime;  //timestamp when the console line was created
     343             :     };
     344             :     std::deque<cline> conlines; //global storage of console lines
     345             : 
     346             :     string commandbuf;
     347             :     char *commandaction = nullptr,
     348             :          *commandprompt = nullptr;
     349             :     enum CommandFlags
     350             :     {
     351             :         CmdFlags_Complete = 1<<0,
     352             :         CmdFlags_Execute  = 1<<1,
     353             :     };
     354             : 
     355             :     int commandflags = 0,
     356             :         commandpos = -1;
     357             : 
     358           0 :     VARFP(maxcon, 10, 200, maxconsolelines,
     359             :     {
     360             :         while(static_cast<int>(conlines.size()) > maxcon)
     361             :         {
     362             :             delete[] conlines.front().line;
     363             :             conlines.pop_back();
     364             :         }
     365             :     });
     366             : 
     367             :     constexpr int constrlen = 512;
     368             : 
     369             :     // tab-completion of all idents and base maps
     370             : 
     371             :     CompletionFinder cfinder;
     372             : 
     373         698 :     void conline(int type, const char *sf)        // add a line to the console buffer
     374             :     {
     375         698 :         char *buf = static_cast<int>(conlines.size()) >= maxcon ? conlines.back().line : newstring("", constrlen-1);
     376         698 :         if(static_cast<int>(conlines.size()) >= maxcon)
     377             :         {
     378         401 :             conlines.pop_back();
     379             :         }
     380             :         cline cl;
     381         698 :         cl.line = buf;
     382         698 :         cl.type = type;
     383         698 :         cl.outtime = totalmillis;                // for how long to keep line on screen
     384         698 :         copystring(cl.line, sf, constrlen);
     385         698 :         conlines.push_front(cl);
     386         698 :     }
     387             : 
     388           1 :     void fullconsole(int *val, int *numargs, ident *id)
     389             :     {
     390           1 :         if(*numargs > 0)
     391             :         {
     392           0 :             UI::holdui("fullconsole", *val!=0);
     393             :         }
     394             :         else
     395             :         {
     396           1 :             int vis = UI::uivisible("fullconsole") ? 1 : 0;
     397           1 :             if(*numargs < 0)
     398             :             {
     399           0 :                 intret(vis);
     400             :             }
     401             :             else
     402             :             {
     403           1 :                 printvar(id, vis);
     404             :             }
     405             :         }
     406           1 :     }
     407             : 
     408           1 :     void toggleconsole()
     409             :     {
     410           1 :         UI::toggleui("fullconsole");
     411           1 :     }
     412             : 
     413             :     VARP(miniconsize, 0, 5, 100);               //miniature console font size
     414             :     VARP(miniconwidth, 0, 40, 100);             //miniature console width
     415             :     VARP(confade, 0, 30, 60);                   //seconds before fading console
     416             :     VARP(miniconfade, 0, 30, 60);
     417             :     HVARP(confilter, 0, 0xFFFFFF, 0xFFFFFF);
     418             :     HVARP(fullconfilter, 0, 0xFFFFFF, 0xFFFFFF);
     419             :     HVARP(miniconfilter, 0, 0, 0xFFFFFF);
     420             : 
     421             :     int conskip     = 0,
     422             :         miniconskip = 0;
     423             : 
     424           2 :     void setconskip(int &skip, int filter, int n)
     425             :     {
     426           2 :         int offsetnum = std::abs(n),
     427           2 :             dir = n < 0 ? -1 : 1;
     428           2 :         skip = std::clamp(skip, 0, static_cast<int>(conlines.size()-1));
     429           2 :         while(offsetnum)
     430             :         {
     431           0 :             skip += dir;
     432           0 :             if(!(static_cast<int>(conlines.size()) > skip))
     433             :             {
     434           0 :                 skip = std::clamp(skip, 0, static_cast<int>(conlines.size()-1));
     435           0 :                 return;
     436             :             }
     437           0 :             if(skip < 0)
     438             :             {
     439           0 :                 skip = 0;
     440           0 :                 break;
     441             :             }
     442           0 :             if(conlines[skip].type&filter)
     443             :             {
     444           0 :                 --offsetnum;
     445             :             }
     446             :         }
     447             :     }
     448             : 
     449           1 :     void clearconsole()
     450             :     {
     451         201 :         while(conlines.size())
     452             :         {
     453         200 :             delete[] conlines.back().line;
     454         200 :             conlines.pop_back();
     455             :         }
     456           1 :     }
     457             : 
     458           0 :     float drawconlines(int conskip, int confade, float conwidth, float conheight, float conoff, int filter, float y = 0, int dir = 1)
     459             :     {
     460           0 :         int numl = conlines.size(),
     461           0 :             offsetlines = std::min(conskip, numl);
     462           0 :         if(confade)
     463             :         {
     464           0 :             if(!conskip)
     465             :             {
     466           0 :                 numl = 0;
     467           0 :                 for(int i = conlines.size(); --i >=0;) //note reverse iteration
     468             :                 {
     469           0 :                     if(totalmillis-conlines[i].outtime < confade*1000)
     470             :                     {
     471           0 :                         numl = i+1;
     472           0 :                         break;
     473             :                     }
     474             :                 }
     475             :             }
     476             :             else
     477             :             {
     478           0 :                 offsetlines--;
     479             :             }
     480             :         }
     481             : 
     482           0 :         int totalheight = 0;
     483           0 :         for(int i = 0; i < numl; ++i) //determine visible height
     484             :         {
     485             :             // shuffle backwards to fill if necessary
     486           0 :             int idx = offsetlines+i < numl ? offsetlines+i : --offsetlines;
     487           0 :             if(!(conlines[idx].type&filter))
     488             :             {
     489           0 :                 continue;
     490             :             }
     491           0 :             char *line = conlines[idx].line;
     492             :             float width, height;
     493           0 :             text_boundsf(line, width, height, conwidth);
     494           0 :             if(totalheight + height > conheight)
     495             :             {
     496           0 :                 numl = i;
     497           0 :                 if(offsetlines == idx)
     498             :                 {
     499           0 :                     ++offsetlines;
     500             :                 }
     501           0 :                 break;
     502             :             }
     503           0 :             totalheight += height;
     504             :         }
     505           0 :         if(dir > 0)
     506             :         {
     507           0 :             y = conoff;
     508             :         }
     509           0 :         for(int i = 0; i < numl; ++i)
     510             :         {
     511           0 :             int idx = offsetlines + (dir > 0 ? numl-i-1 : i);
     512           0 :             if(!(conlines[idx].type&filter))
     513             :             {
     514           0 :                 continue;
     515             :             }
     516           0 :             char *line = conlines[idx].line;
     517             :             float width, height;
     518           0 :             text_boundsf(line, width, height, conwidth);
     519           0 :             if(dir <= 0)
     520             :             {
     521           0 :                 y -= height;
     522             :             }
     523             :             //draw_text(line, conoff, y, 0xFF, 0xFF, 0xFF, 0xFF, -1, conwidth);
     524           0 :             ttr.fontsize(50);
     525           0 :             ttr.renderttf(line, {0xFF, 0xFF, 0xFF, 0}, conoff, y);
     526           0 :             if(dir > 0)
     527             :             {
     528           0 :                 y += height;
     529             :             }
     530             :         }
     531           0 :         return y+conoff;
     532             :     }
     533             : 
     534             :     // keymap is defined externally in keymap.cfg
     535             : 
     536             :     /*
     537             :      * defines a mapping for a single key
     538             :      * multiple keymap objects are aggregated in keyms to create the entire bindings list
     539             :      */
     540             :     struct KeyMap
     541             :     {
     542             :         enum
     543             :         {
     544             :             Action_Default = 0,
     545             :             Action_Spectator,
     546             :             Action_Editing,
     547             :             Action_NumActions
     548             :         };
     549             : 
     550             :         int code;                           //unique bind code assigned to the key
     551             :         char *name;                         //name to use to access this key
     552             :         char *actions[Action_NumActions];   //array of strings to execute depending on what mode is being used
     553             :         bool pressed;                       //whether this key is currently depressed
     554             : 
     555           1 :         KeyMap() : code(-1), name(nullptr), pressed(false)
     556             :         {
     557           4 :             for(int i = 0; i < Action_NumActions; ++i)
     558             :             {
     559           3 :                 actions[i] = newstring("");
     560             :             }
     561           1 :         }
     562           1 :         ~KeyMap()
     563             :         {
     564           1 :             delete[] name;
     565           1 :             name = nullptr;
     566           4 :             for(int i = 0; i < Action_NumActions; ++i)
     567             :             {
     568           3 :                 delete[] actions[i];
     569           3 :                 actions[i] = nullptr;
     570             :             }
     571           1 :         }
     572             : 
     573             :         void clear(int type);
     574           0 :         void clear()
     575             :         {
     576           0 :             for(int i = 0; i < Action_NumActions; ++i)
     577             :             {
     578           0 :                 clear(i);
     579             :             }
     580           0 :         }
     581             :     };
     582             : 
     583             : 
     584             :     KeyMap *keypressed = nullptr;
     585             :     char *keyaction = nullptr;
     586             : 
     587           0 :     void KeyMap::clear(int type)
     588             :     {
     589           0 :         char *&binding = actions[type];
     590           0 :         if(binding[0])
     591             :         {
     592           0 :             if(!keypressed || keyaction!=binding)
     593             :             {
     594           0 :                 delete[] binding;
     595             :             }
     596           0 :             binding = newstring("");
     597             :         }
     598           0 :     }
     599             : 
     600             :     std::map<int, KeyMap> keyms;
     601             : 
     602           1 :     void keymap(int *code, char *key)
     603             :     {
     604           1 :         if(identflags&Idf_Overridden)
     605             :         {
     606           0 :             conoutf(Console_Error, "cannot override keymap %d", *code);
     607           0 :             return;
     608             :         }
     609           1 :         KeyMap &km = keyms[*code];
     610           1 :         km.code = *code;
     611           1 :         delete[] km.name;
     612           1 :         km.name = newstring(key);
     613             :     }
     614             : 
     615           3 :     void searchbinds(char *action, int type)
     616             :     {
     617           3 :         std::vector<char> names;
     618           3 :         for(auto &[k, km] : keyms)
     619             :         {
     620           0 :             if(!std::strcmp(km.actions[type], action))
     621             :             {
     622           0 :                 if(names.size())
     623             :                 {
     624           0 :                     names.push_back(' ');
     625             :                 }
     626           0 :                 for(uint i = 0; i < std::strlen(km.name); ++i)
     627             :                 {
     628           0 :                     names.push_back(km.name[i]);
     629             :                 }
     630             :             }
     631             :         }
     632           3 :         names.push_back('\0');
     633           3 :         result(names.data());
     634           3 :     }
     635             : 
     636           6 :     KeyMap *findbind(const char *key)
     637             :     {
     638           6 :         for(auto &[k, km] : keyms)
     639             :         {
     640           0 :             if(!strcasecmp(km.name, key))
     641             :             {
     642           0 :                 return &km;
     643             :             }
     644             :         }
     645           6 :         return nullptr;
     646             :     }
     647             : 
     648           3 :     void getbind(const char *key, int type)
     649             :     {
     650           3 :         KeyMap *km = findbind(key);
     651           3 :         result(km ? km->actions[type] : "");
     652           3 :     }
     653             : 
     654           3 :     void bindkey(const char *key, const char *action, int state, const char *cmd)
     655             :     {
     656           3 :         if(identflags&Idf_Overridden)
     657             :         {
     658           0 :             conoutf(Console_Error, "cannot override %s \"%s\"", cmd, key);
     659           0 :             return;
     660             :         }
     661           3 :         KeyMap *km = findbind(key);
     662           3 :         if(!km)
     663             :         {
     664           3 :             conoutf(Console_Error, "unknown key \"%s\"", key);
     665           3 :             return;
     666             :         }
     667           0 :         char *&binding = km->actions[state];
     668           0 :         if(!keypressed || keyaction!=binding)
     669             :         {
     670           0 :             delete[] binding;
     671             :         }
     672             :         // trim white-space to make searchbinds more reliable
     673           0 :         while(iscubespace(*action))
     674             :         {
     675           0 :             action++;
     676             :         }
     677           0 :         int len = std::strlen(action);
     678           0 :         while(len>0 && iscubespace(action[len-1]))
     679             :         {
     680           0 :             len--;
     681             :         }
     682           0 :         binding = newstring(action, len);
     683             :     }
     684             : 
     685           2 :     void inputcommand(char *init, char *action = nullptr, char *prompt = nullptr, char *flags = nullptr) // turns input to the command line on or off
     686             :     {
     687           2 :         commandmillis = init ? totalmillis : -1;
     688           2 :         textinput(commandmillis >= 0, TextInput_Console);
     689           2 :         keyrepeat(commandmillis >= 0, KeyRepeat_Console);
     690           2 :         copystring(commandbuf, init ? init : "");
     691             : 
     692           2 :         delete[] commandaction;
     693           2 :         delete[] commandprompt;
     694           2 :         commandaction = nullptr;
     695           2 :         commandprompt = nullptr;
     696             : 
     697           2 :         commandpos = -1;
     698           2 :         if(action && action[0])
     699             :         {
     700           0 :             commandaction = newstring(action);
     701             :         }
     702           2 :         if(prompt && prompt[0])
     703             :         {
     704           0 :             commandprompt = newstring(prompt);
     705             :         }
     706           2 :         commandflags = 0;
     707           2 :         if(flags)
     708             :         {
     709           1 :             while(*flags)
     710             :             {
     711           0 :                 switch(*flags++)
     712             :                 {
     713           0 :                     case 'c':
     714             :                     {
     715           0 :                         commandflags |= CmdFlags_Complete;
     716           0 :                         break;
     717             :                     }
     718           0 :                     case 'x':
     719             :                     {
     720           0 :                         commandflags |= CmdFlags_Execute;
     721           0 :                         break;
     722             :                     }
     723           0 :                     case 's':
     724             :                     {
     725           0 :                         commandflags |= CmdFlags_Complete|CmdFlags_Execute;
     726           0 :                         break;
     727             :                     }
     728             :                 }
     729             :             }
     730             :         }
     731           1 :         else if(init)
     732             :         {
     733           1 :             commandflags |= CmdFlags_Complete|CmdFlags_Execute;
     734             :         }
     735           2 :     }
     736             : 
     737           1 :     void saycommand(char *init)
     738             :     {
     739           1 :         inputcommand(init);
     740           1 :     }
     741             : 
     742           0 :     void pasteconsole()
     743             :     {
     744           0 :         if(!SDL_HasClipboardText())
     745             :         {
     746           0 :             return;
     747             :         }
     748           0 :         char *cb = SDL_GetClipboardText();
     749           0 :         if(!cb)
     750             :         {
     751           0 :             return;
     752             :         }
     753           0 :         size_t cblen = std::strlen(cb),
     754           0 :                commandlen = std::strlen(commandbuf);
     755           0 :         if(strlen(commandbuf) + cblen < 260)
     756             :         {
     757           0 :             std::memcpy(reinterpret_cast<uchar *>(&commandbuf[commandlen]), cb, cblen);
     758             :         }
     759           0 :         commandbuf[commandlen + cblen] = '\0';
     760           0 :         SDL_free(cb);
     761             :     }
     762             : 
     763             :     struct HLine
     764             :     {
     765             :         char *buf, *action, *prompt;
     766             :         int flags;
     767             : 
     768           0 :         HLine() : buf(nullptr), action(nullptr), prompt(nullptr), flags(0) {}
     769           0 :         ~HLine()
     770             :         {
     771           0 :             delete[] buf;
     772           0 :             delete[] action;
     773           0 :             delete[] prompt;
     774             : 
     775           0 :             buf = nullptr;
     776           0 :             action = nullptr;
     777           0 :             prompt = nullptr;
     778           0 :         }
     779             : 
     780           0 :         void restore() const
     781             :         {
     782           0 :             copystring(commandbuf, buf);
     783           0 :             if(commandpos >= static_cast<int>(std::strlen(commandbuf)))
     784             :             {
     785           0 :                 commandpos = -1;
     786             :             }
     787             : 
     788           0 :             delete[] commandaction;
     789           0 :             delete[] commandprompt;
     790             : 
     791           0 :             commandaction = nullptr;
     792           0 :             commandprompt = nullptr;
     793             : 
     794           0 :             if(action)
     795             :             {
     796           0 :                 commandaction = newstring(action);
     797             :             }
     798           0 :             if(prompt)
     799             :             {
     800           0 :                 commandprompt = newstring(prompt);
     801             :             }
     802           0 :             commandflags = flags;
     803           0 :         }
     804             : 
     805           0 :         bool shouldsave() const
     806             :         {
     807           0 :             return std::strcmp(commandbuf, buf) ||
     808           0 :                    (commandaction ? !action || std::strcmp(commandaction, action) : action!=nullptr) ||
     809           0 :                    (commandprompt ? !prompt || std::strcmp(commandprompt, prompt) : prompt!=nullptr) ||
     810           0 :                    commandflags != flags;
     811             :         }
     812             : 
     813           0 :         void save()
     814             :         {
     815           0 :             buf = newstring(commandbuf);
     816           0 :             if(commandaction)
     817             :             {
     818           0 :                 action = newstring(commandaction);
     819             :             }
     820           0 :             if(commandprompt)
     821             :             {
     822           0 :                 prompt = newstring(commandprompt);
     823             :             }
     824           0 :             flags = commandflags;
     825           0 :         }
     826             : 
     827           0 :         void run() const
     828             :         {
     829           0 :             if(flags&CmdFlags_Execute && buf[0]=='/')
     830             :             {
     831           0 :                 execute(buf+1);
     832             :             }
     833           0 :             else if(action)
     834             :             {
     835           0 :                 alias("commandbuf", buf);
     836           0 :                 execute(action);
     837             :             }
     838             :             else
     839             :             {
     840           0 :                 conoutf(Console_Info, "%s", buf);
     841             :             }
     842           0 :         }
     843             :     };
     844             :     std::vector<HLine *> history;
     845             :     int histpos = 0;
     846             : 
     847             :     VARP(maxhistory, 0, 1000, 10000);
     848             : 
     849           1 :     void historycmd(int *n)
     850             :     {
     851             :         static bool inhistory = false;
     852           1 :         if(!inhistory && static_cast<int>(history.size()) > *n)
     853             :         {
     854           0 :             inhistory = true;
     855           0 :             history[history.size()-*n-1]->run();
     856           0 :             inhistory = false;
     857             :         }
     858           1 :     }
     859             : 
     860             :     struct releaseaction
     861             :     {
     862             :         KeyMap *key;
     863             :         union
     864             :         {
     865             :             char *action;
     866             :             ident *id;
     867             :         };
     868             :         int numargs;
     869             :         tagval args[3];
     870             :     };
     871             :     std::vector<releaseaction> releaseactions;
     872             : 
     873           1 :     const char *addreleaseaction(char *s)
     874             :     {
     875           1 :         if(!keypressed)
     876             :         {
     877           1 :             delete[] s;
     878           1 :             return nullptr;
     879             :         }
     880           0 :         releaseactions.emplace_back();
     881           0 :         releaseaction &ra = releaseactions.back();
     882           0 :         ra.key = keypressed;
     883           0 :         ra.action = s;
     884           0 :         ra.numargs = -1;
     885           0 :         return keypressed->name;
     886             :     }
     887             : 
     888           1 :     void onrelease(const char *s)
     889             :     {
     890           1 :         addreleaseaction(newstring(s));
     891           1 :     }
     892             : 
     893           0 :     static void execbind(KeyMap &k, bool isdown, int map)
     894             :     {
     895           0 :         for(uint i = 0; i < releaseactions.size(); i++)
     896             :         {
     897           0 :             releaseaction &ra = releaseactions[i];
     898           0 :             if(ra.key==&k)
     899             :             {
     900           0 :                 if(ra.numargs < 0)
     901             :                 {
     902           0 :                     if(!isdown)
     903             :                     {
     904           0 :                         execute(ra.action);
     905             :                     }
     906           0 :                     delete[] ra.action;
     907             :                 }
     908             :                 else
     909             :                 {
     910           0 :                     execute(isdown ? nullptr : ra.id, ra.args, ra.numargs);
     911             :                 }
     912           0 :                 releaseactions.erase(releaseactions.begin() + i);
     913           0 :                 i--;
     914             :             }
     915             :         }
     916           0 :         if(isdown)
     917             :         {
     918           0 :             int state = KeyMap::Action_Default;
     919           0 :             if(!mainmenu)
     920             :             {
     921           0 :                 if(map == 1)
     922             :                 {
     923           0 :                     state = KeyMap::Action_Editing;
     924             :                 }
     925           0 :                 else if(map == 2)
     926             :                 {
     927           0 :                     state = KeyMap::Action_Spectator;
     928             :                 }
     929             :             }
     930           0 :             char *&action = k.actions[state][0] ? k.actions[state] : k.actions[KeyMap::Action_Default];
     931           0 :             keyaction = action;
     932           0 :             keypressed = &k;
     933           0 :             execute(keyaction);
     934           0 :             keypressed = nullptr;
     935           0 :             if(keyaction!=action)
     936             :             {
     937           0 :                 delete[] keyaction;
     938             :             }
     939             :         }
     940           0 :         k.pressed = isdown;
     941           0 :     }
     942             : 
     943           0 :     bool consoleinput(const char *str, int len)
     944             :     {
     945           0 :         if(commandmillis < 0)
     946             :         {
     947           0 :             return false;
     948             :         }
     949           0 :         ::cfinder.resetcomplete();
     950           0 :         int cmdlen = static_cast<int>(std::strlen(commandbuf)),
     951           0 :             cmdspace = static_cast<int>(sizeof(commandbuf)) - (cmdlen+1);
     952           0 :         len = std::min(len, cmdspace);
     953           0 :         if(commandpos<0)
     954             :         {
     955           0 :             std::memcpy(&commandbuf[cmdlen], str, len);
     956             :         }
     957             :         else
     958             :         {
     959           0 :             std::memmove(&commandbuf[commandpos+len], &commandbuf[commandpos], cmdlen - commandpos);
     960           0 :             std::memcpy(&commandbuf[commandpos], str, len);
     961           0 :             commandpos += len;
     962             :         }
     963           0 :         commandbuf[cmdlen + len] = '\0';
     964             : 
     965           0 :         return true;
     966             :     }
     967             : 
     968           0 :     bool consolekey(int code, bool isdown)
     969             :     {
     970           0 :         if(commandmillis < 0)
     971             :         {
     972           0 :             return false;
     973             :         }
     974           0 :         if(isdown)
     975             :         {
     976           0 :             switch(code)
     977             :             {
     978           0 :                 case SDLK_RETURN:
     979             :                 case SDLK_KP_ENTER:
     980             :                 {
     981           0 :                     break;
     982             :                 }
     983           0 :                 case SDLK_HOME:
     984             :                 {
     985           0 :                     if(std::strlen(commandbuf))
     986             :                     {
     987           0 :                         commandpos = 0;
     988             :                     }
     989           0 :                     break;
     990             :                 }
     991           0 :                 case SDLK_END:
     992             :                 {
     993           0 :                     commandpos = -1;
     994           0 :                     break;
     995             :                 }
     996           0 :                 case SDLK_DELETE:
     997             :                 {
     998           0 :                     size_t len = std::strlen(commandbuf);
     999           0 :                     if(commandpos<0)
    1000             :                     {
    1001           0 :                         break;
    1002             :                     }
    1003           0 :                     std::memmove(&commandbuf[commandpos], &commandbuf[commandpos+1], len - commandpos);
    1004           0 :                     ::cfinder.resetcomplete();
    1005           0 :                     if(commandpos >= static_cast<int>(len-1))
    1006             :                     {
    1007           0 :                         commandpos = -1;
    1008             :                     }
    1009           0 :                     break;
    1010             :                 }
    1011           0 :                 case SDLK_BACKSPACE:
    1012             :                 {
    1013           0 :                     size_t len = std::strlen(commandbuf);
    1014           0 :                     int i = commandpos>=0 ? commandpos : len;
    1015           0 :                     if(i<1)
    1016             :                     {
    1017           0 :                         break;
    1018             :                     }
    1019           0 :                     std::memmove(&commandbuf[i-1], &commandbuf[i], len - i + 1);
    1020           0 :                     ::cfinder.resetcomplete();
    1021           0 :                     if(commandpos>0)
    1022             :                     {
    1023           0 :                         commandpos--;
    1024             :                     }
    1025           0 :                     else if(!commandpos && len<=1)
    1026             :                     {
    1027           0 :                         commandpos = -1;
    1028             :                     }
    1029           0 :                     break;
    1030             :                 }
    1031           0 :                 case SDLK_LEFT:
    1032             :                 {
    1033           0 :                     if(commandpos>0)
    1034             :                     {
    1035           0 :                         commandpos--;
    1036             :                     }
    1037           0 :                     else if(commandpos<0)
    1038             :                     {
    1039           0 :                         commandpos = static_cast<int>(std::strlen(commandbuf))-1;
    1040             :                     }
    1041           0 :                     break;
    1042             :                 }
    1043           0 :                 case SDLK_RIGHT:
    1044             :                 {
    1045           0 :                     if(commandpos>=0 && ++commandpos >= static_cast<int>(std::strlen(commandbuf)))
    1046             :                     {
    1047           0 :                         commandpos = -1;
    1048             :                     }
    1049           0 :                     break;
    1050             :                 }
    1051           0 :                 case SDLK_UP:
    1052             :                 {
    1053           0 :                     if(histpos > static_cast<int>(history.size()))
    1054             :                     {
    1055           0 :                         histpos = history.size();
    1056             :                     }
    1057           0 :                     if(histpos > 0)
    1058             :                     {
    1059           0 :                         history[--histpos]->restore();
    1060             :                     }
    1061           0 :                     break;
    1062             :                 }
    1063           0 :                 case SDLK_DOWN:
    1064             :                 {
    1065           0 :                     if(histpos + 1 < static_cast<int>(history.size()))
    1066             :                     {
    1067           0 :                         history[++histpos]->restore();
    1068             :                     }
    1069           0 :                     break;
    1070             :                 }
    1071           0 :                 case SDLK_TAB:
    1072             :                 {
    1073           0 :                     if(commandflags&CmdFlags_Complete)
    1074             :                     {
    1075           0 :                         ::cfinder.complete(commandbuf, sizeof(commandbuf), commandflags&CmdFlags_Execute ? "/" : nullptr);
    1076           0 :                         if(commandpos>=0 && commandpos >= static_cast<int>(std::strlen(commandbuf)))
    1077             :                         {
    1078           0 :                             commandpos = -1;
    1079             :                         }
    1080             :                     }
    1081           0 :                     break;
    1082             :                 }
    1083           0 :                 case SDLK_v:
    1084             :                 {
    1085           0 :                     if(SDL_GetModState()&(KMOD_LCTRL|KMOD_RCTRL))//mod keys
    1086             :                     {
    1087           0 :                         pasteconsole();
    1088             :                     }
    1089           0 :                     break;
    1090             :                 }
    1091             :             }
    1092             :         }
    1093             :         else
    1094             :         {
    1095           0 :             if(code==SDLK_RETURN || code==SDLK_KP_ENTER)
    1096             :             {
    1097           0 :                 HLine *h = nullptr;
    1098           0 :                 if(commandbuf[0])
    1099             :                 {
    1100           0 :                     if(history.empty() || history.back()->shouldsave())
    1101             :                     {
    1102           0 :                         if(maxhistory && static_cast<int>(history.size()) >= maxhistory)
    1103             :                         {
    1104           0 :                             for(uint i = 0; i < (history.size()-maxhistory+1); ++i)
    1105             :                             {
    1106           0 :                                 delete history[i];
    1107             :                             }
    1108           0 :                             history.erase(history.begin(), history.begin() + history.size()-maxhistory+1);
    1109             :                         }
    1110           0 :                         history.emplace_back(h = new HLine)->save();
    1111             :                     }
    1112             :                     else
    1113             :                     {
    1114           0 :                         h = history.back();
    1115             :                     }
    1116             :                 }
    1117           0 :                 histpos = history.size();
    1118           0 :                 inputcommand(nullptr);
    1119           0 :                 if(h)
    1120             :                 {
    1121           0 :                     h->run();
    1122             :                 }
    1123           0 :             }
    1124           0 :             else if(code==SDLK_ESCAPE)
    1125             :             {
    1126           0 :                 histpos = history.size();
    1127           0 :                 inputcommand(nullptr);
    1128             :             }
    1129             :         }
    1130             : 
    1131           0 :         return true;
    1132             :     }
    1133             : }
    1134             : 
    1135             : //iengine.h
    1136           0 : void clear_console()
    1137             : {
    1138           0 :     keyms.clear();
    1139           0 : }
    1140             : 
    1141             : //console.h
    1142           0 : void processtextinput(const char *str, int len)
    1143             : {
    1144           0 :     if(!UI::textinput(str, len))
    1145             :     {
    1146           0 :         consoleinput(str, len);
    1147             :     }
    1148           0 : }
    1149             : 
    1150           0 : void processkey(int code, bool isdown, int map)
    1151             : {
    1152           0 :     auto itr = keyms.find(code);
    1153           0 :     if(itr != keyms.end() && (*itr).second.pressed)
    1154             :     {
    1155           0 :         execbind((*itr).second, isdown, map); // allow pressed keys to release
    1156             :     }
    1157           0 :     else if(!UI::keypress(code, isdown)) // UI key intercept
    1158             :     {
    1159           0 :         if(!consolekey(code, isdown))
    1160             :         {
    1161           0 :             if(itr != keyms.end())
    1162             :             {
    1163           0 :                 execbind((*itr).second, isdown, map);
    1164             :             }
    1165             :         }
    1166             :     }
    1167           0 : }
    1168             : 
    1169           0 : float rendercommand(float x, float y, float w)
    1170             : {
    1171           0 :     if(commandmillis < 0)
    1172             :     {
    1173           0 :         return 0;
    1174             :     }
    1175             :     char buf[constrlen];
    1176           0 :     const char *prompt = commandprompt ? commandprompt : ">";
    1177           0 :     formatstring(buf, "%s %s", prompt, commandbuf);
    1178             :     float width, height;
    1179           0 :     text_boundsf(buf, width, height, w);
    1180           0 :     y -= height;
    1181           0 :     ttr.fontsize(50);
    1182           0 :     ttr.renderttf(buf, {0xFF, 0xFF, 0xFF, 0}, x, y);
    1183             :     //draw_text(buf, x, y, 0xFF, 0xFF, 0xFF, 0xFF, commandpos>=0 ? commandpos+1 + std::strlen(prompt) : std::strlen(buf), w);
    1184           0 :     return height;
    1185             : }
    1186             : 
    1187           0 : float renderfullconsole(float w, float h)
    1188             : {
    1189           0 :     float conpad = FONTH/2,
    1190           0 :           conheight = h - 2*conpad,
    1191           0 :           conwidth = w - 2*conpad;
    1192           0 :     drawconlines(conskip, 0, conwidth, conheight, conpad, fullconfilter);
    1193           0 :     return conheight + 2*conpad;
    1194             : }
    1195             : 
    1196           0 : float renderconsole(float w, float h, float abovehud)
    1197             : {
    1198           0 :     static VARP(consize, 0, 5, 100);                   //font size of the console text
    1199           0 :     float conpad = FONTH/2,
    1200           0 :           conheight = std::min(static_cast<float>(FONTH*consize), h - 2*conpad),
    1201           0 :           conwidth = w - 2*conpad,
    1202           0 :           y = drawconlines(conskip, confade, conwidth, conheight, conpad, confilter);
    1203           0 :     if(miniconsize && miniconwidth)
    1204             :     {
    1205           0 :         drawconlines(miniconskip, miniconfade, (miniconwidth*(w - 2*conpad))/100, std::min(static_cast<float>(FONTH*miniconsize), abovehud - y), conpad, miniconfilter, abovehud, -1);
    1206             :     }
    1207           0 :     return y;
    1208             : }
    1209             : 
    1210         698 : void conoutfv(int type, const char *fmt, va_list args)
    1211             : {
    1212             :     static char buf[constrlen];
    1213         698 :     vformatstring(buf, fmt, args, sizeof(buf));
    1214         698 :     conline(type, buf);
    1215         698 :     logoutf("%s", buf);
    1216         698 : }
    1217             : 
    1218         575 : void conoutf(const char *fmt, ...)
    1219             : {
    1220             :     va_list args;
    1221         575 :     va_start(args, fmt);
    1222         575 :     conoutfv(Console_Info, fmt, args);
    1223         575 :     va_end(args);
    1224         575 : }
    1225             : 
    1226         117 : void conoutf(int type, const char *fmt, ...)
    1227             : {
    1228             :     va_list args;
    1229         117 :     va_start(args, fmt);
    1230         117 :     conoutfv(type, fmt, args);
    1231         117 :     va_end(args);
    1232         117 : }
    1233             : 
    1234           0 : const char *getkeyname(int code)
    1235             : {
    1236           0 :     auto itr = keyms.find(code);
    1237           0 :     return itr != keyms.end() ? (*itr).second.name : nullptr;
    1238             : }
    1239             : 
    1240           1 : tagval *addreleaseaction(ident *id, int numargs)
    1241             : {
    1242           1 :     if(!keypressed || numargs > 3)
    1243             :     {
    1244           1 :         return nullptr;
    1245             :     }
    1246           0 :     releaseactions.emplace_back();
    1247           0 :     releaseaction &ra = releaseactions.back();
    1248           0 :     ra.key = keypressed;
    1249           0 :     ra.id = id;
    1250           0 :     ra.numargs = numargs;
    1251           0 :     return ra.args;
    1252             : }
    1253             : 
    1254             : //print to a stream f the binds in the binds vector
    1255           0 : void writebinds(std::fstream& f)
    1256             : {
    1257             :     static const char * const cmds[3] = { "bind", "specbind", "editbind" };
    1258           0 :     std::vector<KeyMap *> binds;
    1259           0 :     for(auto &[k, km] : keyms)
    1260             :     {
    1261           0 :         binds.push_back(&km);
    1262             :     }
    1263           0 :     std::sort(binds.begin(), binds.end());
    1264           0 :     for(int j = 0; j < 3; ++j)
    1265             :     {
    1266           0 :         for(KeyMap *&km : binds)
    1267             :         {
    1268           0 :             if(*(km->actions[j]))
    1269             :             {
    1270           0 :                 if(validateblock(km->actions[j]))
    1271             :                 {
    1272           0 :                     f << cmds[j] << " " << escapestring(km->name) << " [" << km->actions[j] << "]\n";
    1273             :                 }
    1274             :                 else
    1275             :                 {
    1276           0 :                     f << cmds[j] << " " << escapestring(km->name) << " " << escapestring(km->actions[j]) << std::endl;
    1277             :                 }
    1278             :             }
    1279             :         }
    1280             :     }
    1281           0 : }
    1282             : 
    1283           0 : extern void writecompletions(std::fstream& f)
    1284             : {
    1285           0 :     ::cfinder.writecompletions(f);
    1286           0 : }
    1287             : 
    1288           1 : void initconsolecmds()
    1289             : {
    1290           1 :     addcommand("fullconsole", reinterpret_cast<identfun>(fullconsole), "iN$", Id_Command);
    1291           1 :     addcommand("toggleconsole", reinterpret_cast<identfun>(toggleconsole), "", Id_Command);
    1292             : 
    1293           1 :     static auto conskipcmd = [] (int *n)
    1294             :     {
    1295           1 :         setconskip(conskip, UI::uivisible("fullconsole") ? fullconfilter : confilter, *n);
    1296           1 :     };
    1297           1 :     addcommand("conskip", reinterpret_cast<identfun>(+conskipcmd), "i", Id_Command);
    1298             : 
    1299             : 
    1300           1 :     static auto miniconskipcmd = [] (int *n)
    1301             :     {
    1302           1 :         setconskip(miniconskip, miniconfilter, *n);
    1303           1 :     };
    1304           1 :     addcommand("miniconskip", reinterpret_cast<identfun>(+miniconskipcmd), "i", Id_Command);
    1305             : 
    1306           1 :     addcommand("clearconsole", reinterpret_cast<identfun>(clearconsole), "", Id_Command);
    1307           1 :     addcommand("keymap", reinterpret_cast<identfun>(keymap), "is", Id_Command);
    1308             : 
    1309           1 :     static auto bind = [] (char *key, char *action)
    1310             :     {
    1311           1 :         bindkey(key, action, KeyMap::Action_Default, "bind");
    1312           1 :     };
    1313           1 :     addcommand("bind", reinterpret_cast<identfun>(+bind), "ss", Id_Command);
    1314             : 
    1315           1 :     static auto specbind = [] (char *key, char *action)
    1316             :     {
    1317           1 :         bindkey(key, action, KeyMap::Action_Spectator, "specbind");
    1318           1 :     };
    1319           1 :     addcommand("specbind", reinterpret_cast<identfun>(+specbind), "ss", Id_Command);
    1320             : 
    1321           1 :     static auto editbind = [] (char *key, char *action)
    1322             :     {
    1323           1 :         bindkey(key, action, KeyMap::Action_Editing, "editbind");
    1324           1 :     };
    1325           1 :     addcommand("editbind", reinterpret_cast<identfun>(+editbind), "ss", Id_Command);
    1326             : 
    1327           1 :     static auto getbindcmd = [] (char *key)
    1328             :     {
    1329           1 :         getbind(key, KeyMap::Action_Default);
    1330           1 :     };
    1331           1 :     addcommand("getbind", reinterpret_cast<identfun>(+getbindcmd), "s", Id_Command);
    1332             : 
    1333           1 :     static auto getspecbind = [] (char *key)
    1334             :     {
    1335           1 :         getbind(key, KeyMap::Action_Spectator);
    1336           1 :     };
    1337           1 :     addcommand("getspecbind", reinterpret_cast<identfun>(+getspecbind), "s", Id_Command);
    1338             : 
    1339           1 :     static auto geteditbind = [] (char *key)
    1340             :     {
    1341           1 :         getbind(key, KeyMap::Action_Editing);
    1342           1 :     };
    1343           1 :     addcommand("geteditbind", reinterpret_cast<identfun>(+geteditbind), "s", Id_Command);
    1344             : 
    1345           1 :     static auto searchbindscmd = [] (char *action)
    1346             :     {
    1347           1 :         searchbinds(action, KeyMap::Action_Default);
    1348           1 :     };
    1349           1 :     addcommand("searchbinds", reinterpret_cast<identfun>(+searchbindscmd), "s", Id_Command);
    1350             : 
    1351           1 :     static auto searchspecbinds = [] (char *action)
    1352             :     {
    1353           1 :         searchbinds(action, KeyMap::Action_Spectator);
    1354           1 :     };
    1355           1 :     addcommand("searchspecbinds", reinterpret_cast<identfun>(+searchspecbinds), "s", Id_Command);
    1356             : 
    1357           1 :     static auto searcheditbinds = [] (char *action)
    1358             :     {
    1359           1 :         searchbinds(action, KeyMap::Action_Editing);
    1360           1 :     };
    1361           1 :     addcommand("searcheditbinds", reinterpret_cast<identfun>(+searcheditbinds), "s", Id_Command);
    1362             : 
    1363           1 :     static auto clearbinds = [] ()
    1364             :     {
    1365           1 :         for(auto &[k, km] : keyms)
    1366             :         {
    1367           0 :             km.clear(KeyMap::Action_Default);
    1368             :         }
    1369           1 :     };
    1370           1 :     addcommand("clearbinds", reinterpret_cast<identfun>(+clearbinds), "", Id_Command);
    1371             : 
    1372           1 :     static auto clearspecbinds = [] ()
    1373             :     {
    1374           1 :         for(auto &[k, km] : keyms)
    1375             :         {
    1376           0 :             km.clear(KeyMap::Action_Spectator);
    1377             :         }
    1378           1 :     };
    1379           1 :     addcommand("clearspecbinds", reinterpret_cast<identfun>(+clearspecbinds), "", Id_Command);
    1380             : 
    1381           1 :     static auto cleareditbinds = [] ()
    1382             :     {
    1383           1 :         for(auto &[k, km] : keyms)
    1384             :         {
    1385           0 :             km.clear(KeyMap::Action_Editing);
    1386             :         }
    1387           1 :     };
    1388           1 :     addcommand("cleareditbinds", reinterpret_cast<identfun>(+cleareditbinds), "", Id_Command);
    1389             : 
    1390           1 :     static auto clearallbinds = [] ()
    1391             :     {
    1392           1 :         for(auto &[k, km] : keyms)
    1393             :         {
    1394           0 :             km.clear();
    1395             :         }
    1396           1 :     };
    1397           1 :     addcommand("clearallbinds", reinterpret_cast<identfun>(+clearallbinds), "", Id_Command);
    1398           1 :     addcommand("inputcommand", reinterpret_cast<identfun>(inputcommand), "ssss", Id_Command);
    1399           1 :     addcommand("saycommand", reinterpret_cast<identfun>(saycommand), "C", Id_Command);
    1400           1 :     addcommand("history", reinterpret_cast<identfun>(historycmd), "i", Id_Command);
    1401           1 :     addcommand("onrelease", reinterpret_cast<identfun>(onrelease), "s", Id_Command);
    1402           2 :     addcommand("complete", reinterpret_cast<identfun>(+[] (char *command, char *dir, char *ext) {::cfinder.addfilecomplete(command, dir, ext);}), "sss", Id_Command);
    1403           2 :     addcommand("listcomplete", reinterpret_cast<identfun>(+[] (char *command, char *list) {::cfinder.addlistcomplete(command, list);}), "ss", Id_Command);
    1404           1 : }

Generated by: LCOV version 1.14