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