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