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 : }
|