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

Generated by: LCOV version 1.14