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