erebus

annotate src/console.cc @ 42:b9294cd6b9dc

console input history with up/down
author John Tsiombikas <nuclear@member.fsf.org>
date Tue, 10 Jun 2014 16:15:08 +0300
parents 2e817711d0f6
children
rev   line source
nuclear@37 1 #include <stdio.h>
nuclear@37 2 #include <stdlib.h>
nuclear@37 3 #include <string.h>
nuclear@37 4 #include <stdarg.h>
nuclear@37 5 #include <assert.h>
nuclear@37 6 #include <algorithm>
nuclear@37 7 #include "opengl.h"
nuclear@37 8 #include "console.h"
nuclear@37 9
nuclear@37 10 Console::Console()
nuclear@41 11 : prompt("> ")
nuclear@37 12 {
nuclear@37 13 visible = false;
nuclear@37 14 nlines = 16;
nuclear@37 15 ncolumns = 80;
nuclear@37 16
nuclear@37 17 inpq_front = inpq_back = 0;
nuclear@37 18
nuclear@37 19 max_hist_lines = 64;
nuclear@37 20 max_output_lines = 128;
nuclear@37 21
nuclear@42 22 hist_iter_valid = false;
nuclear@42 23
nuclear@37 24 echo = true;
nuclear@37 25 font = 0;
nuclear@37 26 font_size = 0;
nuclear@37 27
nuclear@37 28 cursor = 0;
nuclear@41 29 input_win = 0;
nuclear@41 30 }
nuclear@41 31
nuclear@41 32 void Console::set_cursor(int x)
nuclear@41 33 {
nuclear@41 34 if(x < 0 || x > (int)input.size()) {
nuclear@41 35 return;
nuclear@41 36 }
nuclear@41 37 cursor = x;
nuclear@41 38
nuclear@41 39 int max_chars = ncolumns - (int)prompt.size() - 1;
nuclear@41 40
nuclear@41 41 if(cursor < input_win) {
nuclear@41 42 input_win = cursor;
nuclear@41 43 } else if(cursor > input_win + max_chars) {
nuclear@41 44 input_win = std::max(cursor - max_chars, 0);
nuclear@41 45 }
nuclear@41 46 }
nuclear@41 47
nuclear@41 48 int Console::get_cursor() const
nuclear@41 49 {
nuclear@41 50 return cursor;
nuclear@37 51 }
nuclear@37 52
nuclear@37 53 void Console::set_font(dtx_font *font, int sz)
nuclear@37 54 {
nuclear@37 55 this->font = font;
nuclear@37 56 font_size = sz;
nuclear@37 57 }
nuclear@37 58
nuclear@37 59 void Console::set_history_size(int hsz)
nuclear@37 60 {
nuclear@37 61 max_hist_lines = hsz;
nuclear@37 62
nuclear@37 63 int drop = hist.size() - hsz;
nuclear@37 64 if(drop > 0) {
nuclear@37 65 auto it = hist.begin();
nuclear@37 66 for(int i=0; i<drop; i++) {
nuclear@37 67 ++it;
nuclear@37 68 }
nuclear@37 69 hist.erase(hist.begin(), it);
nuclear@37 70 }
nuclear@37 71 }
nuclear@37 72
nuclear@37 73 void Console::set_output_buffer_size(int sz)
nuclear@37 74 {
nuclear@37 75 max_output_lines = sz;
nuclear@37 76
nuclear@37 77 int drop = output.size() - sz;
nuclear@37 78 if(drop > 0) {
nuclear@37 79 output.erase(output.begin(), output.begin() + drop);
nuclear@37 80 }
nuclear@37 81 }
nuclear@37 82
nuclear@37 83 void Console::set_command_func(std::function<void (const char*)> func)
nuclear@37 84 {
nuclear@37 85 cmd_handler = func;
nuclear@37 86 }
nuclear@37 87
nuclear@37 88 void Console::set_echo(bool echo)
nuclear@37 89 {
nuclear@37 90 this->echo = echo;
nuclear@37 91 }
nuclear@37 92
nuclear@37 93 bool Console::get_echo() const
nuclear@37 94 {
nuclear@37 95 return echo;
nuclear@37 96 }
nuclear@37 97
nuclear@37 98 void Console::set_visible(bool v)
nuclear@37 99 {
nuclear@37 100 visible = v;
nuclear@37 101 }
nuclear@37 102
nuclear@37 103 bool Console::is_visible() const
nuclear@37 104 {
nuclear@37 105 return visible;
nuclear@37 106 }
nuclear@37 107
nuclear@37 108 void Console::set_size(int lines, int columns)
nuclear@37 109 {
nuclear@37 110 nlines = lines;
nuclear@37 111 ncolumns = columns;
nuclear@37 112 }
nuclear@37 113
nuclear@37 114 int Console::get_size_lines() const
nuclear@37 115 {
nuclear@37 116 return nlines;
nuclear@37 117 }
nuclear@37 118
nuclear@37 119 int Console::get_size_columns() const
nuclear@37 120 {
nuclear@37 121 return ncolumns;
nuclear@37 122 }
nuclear@37 123
nuclear@37 124 bool Console::update()
nuclear@37 125 {
nuclear@37 126 bool must_redraw = false;
nuclear@37 127
nuclear@37 128 while(inpq_front != inpq_back) {
nuclear@37 129 int c = keybuf[inpq_front];
nuclear@37 130 inpq_front = (inpq_front + 1) % INPUTQ_SIZE;
nuclear@37 131
nuclear@37 132 switch(c) {
nuclear@37 133 case '\n':
nuclear@37 134 case '\r':
nuclear@37 135 if(echo) {
nuclear@37 136 puts(input.c_str());
nuclear@37 137 putchar('\n');
nuclear@37 138 }
nuclear@41 139 if(!input.empty() && cmd_handler) {
nuclear@41 140 cmd_handler(input.c_str());
nuclear@41 141 }
nuclear@37 142
nuclear@37 143 hist.push_back(std::move(input)); // move the input string into the history buffer
nuclear@37 144 if((int)hist.size() > max_hist_lines) {
nuclear@37 145 hist.pop_front();
nuclear@37 146 }
nuclear@42 147 hist_iter_valid = false;
nuclear@42 148
nuclear@41 149 set_cursor(0);
nuclear@37 150 must_redraw = true;
nuclear@37 151 break;
nuclear@37 152
nuclear@37 153 case '\t':
nuclear@37 154 // TODO: completion
nuclear@37 155 break;
nuclear@37 156
nuclear@37 157 case '\b':
nuclear@37 158 if(!input.empty()) {
nuclear@37 159 if(cursor == (int)input.size()) {
nuclear@37 160 input.pop_back();
nuclear@41 161 set_cursor(get_cursor() - 1);
nuclear@37 162 must_redraw = true;
nuclear@37 163 } else if(cursor > 0) {
nuclear@38 164 input.erase(cursor - 1, 1);
nuclear@41 165 set_cursor(get_cursor() - 1);
nuclear@37 166 must_redraw = true;
nuclear@37 167 }
nuclear@37 168 }
nuclear@37 169 break;
nuclear@37 170
nuclear@42 171 case KEY_UP:
nuclear@42 172 if(!hist.empty()) {
nuclear@42 173 if(!hist_iter_valid) {
nuclear@42 174 hist_iter = hist.rbegin();
nuclear@42 175 hist_iter_valid = true;
nuclear@42 176 input = *hist_iter;
nuclear@42 177 } else {
nuclear@42 178 if(++hist_iter == hist.rend()) {
nuclear@42 179 --hist_iter;
nuclear@42 180 break;
nuclear@42 181 }
nuclear@42 182 input = *hist_iter;
nuclear@42 183 }
nuclear@42 184 set_cursor(input.size());
nuclear@42 185 }
nuclear@42 186 break;
nuclear@42 187
nuclear@42 188 case KEY_DOWN:
nuclear@42 189 if(!hist.empty()) {
nuclear@42 190 if(!hist_iter_valid) {
nuclear@42 191 hist_iter = hist.rbegin();
nuclear@42 192 hist_iter_valid = true;
nuclear@42 193 }
nuclear@42 194 if(hist_iter != hist.rbegin()) {
nuclear@42 195 input = *--hist_iter;
nuclear@42 196 set_cursor(input.size());
nuclear@42 197 }
nuclear@42 198 }
nuclear@42 199 break;
nuclear@42 200
nuclear@37 201 case KEY_LEFT:
nuclear@37 202 if(cursor > 0) {
nuclear@41 203 set_cursor(get_cursor() - 1);
nuclear@37 204 must_redraw = true;
nuclear@37 205 }
nuclear@37 206 break;
nuclear@37 207
nuclear@37 208 case KEY_RIGHT:
nuclear@37 209 if(cursor < (int)input.size()) {
nuclear@41 210 set_cursor(get_cursor() + 1);
nuclear@37 211 must_redraw = true;
nuclear@37 212 }
nuclear@37 213 break;
nuclear@37 214
nuclear@37 215 case KEY_HOME:
nuclear@41 216 set_cursor(0);
nuclear@37 217 must_redraw = true;
nuclear@37 218 break;
nuclear@37 219
nuclear@37 220 case KEY_END:
nuclear@41 221 set_cursor(input.size());
nuclear@37 222 must_redraw = true;
nuclear@37 223 break;
nuclear@37 224
nuclear@37 225 case KEY_PGUP:
nuclear@37 226 case KEY_PGDOWN:
nuclear@42 227 // TODO scroll output buffer
nuclear@37 228 break;
nuclear@37 229
nuclear@37 230 default:
nuclear@37 231 if(c < 256 && isprint(c)) {
nuclear@38 232 if(cursor == (int)input.size()) {
nuclear@38 233 input.push_back(c);
nuclear@38 234 } else {
nuclear@38 235 input.insert(cursor, 1, c);
nuclear@38 236 }
nuclear@41 237 set_cursor(get_cursor() + 1);
nuclear@37 238 must_redraw = true;
nuclear@37 239 }
nuclear@37 240 }
nuclear@37 241 }
nuclear@37 242 return must_redraw;
nuclear@37 243 }
nuclear@37 244
nuclear@37 245 void Console::draw() const
nuclear@37 246 {
nuclear@37 247 if(!font || !visible) return;
nuclear@37 248 dtx_use_font(font, font_size);
nuclear@37 249
nuclear@37 250 int vp[4];
nuclear@37 251 glGetIntegerv(GL_VIEWPORT, vp);
nuclear@37 252
nuclear@37 253 glMatrixMode(GL_PROJECTION);
nuclear@37 254 glPushMatrix();
nuclear@37 255 glLoadIdentity();
nuclear@37 256 glOrtho(vp[0], vp[0] + vp[2], vp[1], vp[1] + vp[3], -1, 1);
nuclear@37 257
nuclear@37 258 glMatrixMode(GL_MODELVIEW);
nuclear@37 259 glPushMatrix();
nuclear@37 260 glLoadIdentity();
nuclear@41 261 glTranslatef(0, vp[3], 0);
nuclear@37 262
nuclear@37 263 std::string buflines;
nuclear@37 264
nuclear@37 265 int num_lines = std::min(nlines, (int)output.size());
nuclear@37 266 auto it = output.cbegin() + (output.size() - num_lines);
nuclear@37 267
nuclear@37 268 float max_width = dtx_glyph_width('Q') * ncolumns;
nuclear@37 269
nuclear@37 270 while(it != output.cend()) {
nuclear@37 271 const std::string &line = *it++;
nuclear@37 272 buflines += line + std::string("\n");
nuclear@37 273
nuclear@41 274 //max_width = std::max(max_width, dtx_string_width(line.c_str()));
nuclear@37 275 }
nuclear@37 276
nuclear@37 277 // draw the output box
nuclear@38 278 float line_height = dtx_line_height();
nuclear@38 279 float outbox_height = nlines * line_height;
nuclear@41 280 float console_height = outbox_height + line_height * 1.5;
nuclear@37 281
nuclear@37 282 glPushAttrib(GL_ENABLE_BIT);
nuclear@37 283 glEnable(GL_BLEND);
nuclear@40 284 glDisable(GL_TEXTURE_2D);
nuclear@37 285 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
nuclear@37 286
nuclear@41 287 glEnable(GL_SCISSOR_TEST);
nuclear@41 288 glScissor(0, vp[3] - console_height, max_width, console_height);
nuclear@41 289
nuclear@37 290 glBegin(GL_QUADS);
nuclear@37 291 glColor4f(0.1, 0.2, 0.5, 0.7);
nuclear@37 292 glVertex2f(0, -outbox_height);
nuclear@37 293 glVertex2f(max_width, -outbox_height);
nuclear@37 294 glVertex2f(max_width, 0);
nuclear@37 295 glVertex2f(0, 0);
nuclear@37 296
nuclear@37 297 glColor4f(0.5, 0.2, 0.1, 0.7);
nuclear@41 298 glVertex2f(0, -console_height);
nuclear@41 299 glVertex2f(max_width, -console_height);
nuclear@37 300 glVertex2f(max_width, -outbox_height);
nuclear@37 301 glVertex2f(0, -outbox_height);
nuclear@37 302 glEnd();
nuclear@37 303
nuclear@37 304 // draw the output text
nuclear@37 305 glPushMatrix();
nuclear@38 306 glTranslatef(0, -line_height, 0);
nuclear@37 307 glColor4f(1, 1, 1, 0.7);
nuclear@37 308 dtx_string(buflines.c_str());
nuclear@37 309 glPopMatrix();
nuclear@37 310
nuclear@37 311 // draw the input line
nuclear@38 312 glTranslatef(0, -outbox_height - line_height, 0);
nuclear@38 313
nuclear@41 314 std::string inpline = prompt + input.substr(input_win);
nuclear@38 315 dtx_string(inpline.c_str());
nuclear@38 316 glDisable(GL_TEXTURE_2D);
nuclear@38 317
nuclear@41 318 float cursor_x = dtx_char_pos(inpline.c_str(), cursor - input_win + prompt.size());
nuclear@38 319 glBegin(GL_QUADS);
nuclear@38 320 glColor4f(1, 1, 1, 0.7);
nuclear@42 321 glVertex2f(cursor_x, -2);
nuclear@42 322 glVertex2f(cursor_x + 2, -2);
nuclear@38 323 glVertex2f(cursor_x + 2, line_height - 2);
nuclear@38 324 glVertex2f(cursor_x, line_height - 2);
nuclear@38 325 glEnd();
nuclear@37 326
nuclear@37 327 glPopAttrib();
nuclear@37 328
nuclear@37 329 glMatrixMode(GL_PROJECTION);
nuclear@37 330 glPopMatrix();
nuclear@37 331 glMatrixMode(GL_MODELVIEW);
nuclear@37 332 glPopMatrix();
nuclear@37 333 }
nuclear@37 334
nuclear@37 335 void Console::input_key(int key)
nuclear@37 336 {
nuclear@37 337 keybuf[inpq_back] = key;
nuclear@37 338 inpq_back = (inpq_back + 1) % INPUTQ_SIZE;
nuclear@37 339 }
nuclear@37 340
nuclear@37 341 void Console::putchar(char c)
nuclear@37 342 {
nuclear@37 343 if(output.empty()) {
nuclear@37 344 output.push_back(std::string(""));
nuclear@37 345 }
nuclear@37 346
nuclear@37 347 if(c == '\n' || c == '\r') {
nuclear@37 348 output.push_back(std::string(""));
nuclear@37 349 if((int)output.size() > max_output_lines) {
nuclear@37 350 output.erase(output.begin());
nuclear@37 351 }
nuclear@37 352 } else {
nuclear@37 353 output.back().push_back(c);
nuclear@37 354 }
nuclear@37 355 }
nuclear@37 356
nuclear@37 357 void Console::puts(const char *str)
nuclear@37 358 {
nuclear@37 359 while(*str) {
nuclear@37 360 putchar(*str++);
nuclear@37 361 }
nuclear@37 362 }
nuclear@37 363
nuclear@37 364 void Console::printf(const char *fmt, ...)
nuclear@37 365 {
nuclear@37 366 static char buf[1024];
nuclear@37 367 va_list ap;
nuclear@37 368
nuclear@37 369 va_start(ap, fmt);
nuclear@37 370 vsnprintf(buf, sizeof buf, fmt, ap);
nuclear@37 371 va_end(ap);
nuclear@37 372
nuclear@37 373 puts(buf);
nuclear@37 374 }
nuclear@37 375
nuclear@37 376 void Console::debug()
nuclear@37 377 {
nuclear@37 378 fprintf(stderr, "visible: %s\n", visible ? "true" : "false");
nuclear@37 379 fprintf(stderr, "size: %d lines x %d columns\n", nlines, ncolumns);
nuclear@37 380 fprintf(stderr, "cursor: %d\n", cursor);
nuclear@37 381
nuclear@37 382 int qsize = 0;
nuclear@37 383 int qidx = inpq_front;
nuclear@37 384 while(qidx != inpq_back) {
nuclear@37 385 qsize++;
nuclear@37 386 qidx = (qidx + 1) % INPUTQ_SIZE;
nuclear@37 387 }
nuclear@37 388
nuclear@37 389 fprintf(stderr, "input queue size: %d\n", qsize);
nuclear@37 390 fprintf(stderr, "current input line: \"%s\"\n", input.c_str());
nuclear@37 391 fprintf(stderr, "%d saved inputs in the history (max %d)\n", (int)hist.size(), max_hist_lines);
nuclear@37 392 auto it = hist.begin();
nuclear@37 393 int idx = 0;
nuclear@37 394 while(it != hist.end()) {
nuclear@37 395 fprintf(stderr, " h(%d): \"%s\"\n", idx++, it++->c_str());
nuclear@37 396 }
nuclear@37 397
nuclear@37 398 fprintf(stderr, "output buffer (lines: %d/%d):\n", (int)output.size(), max_output_lines);
nuclear@37 399 for(size_t i=0; i<output.size(); i++) {
nuclear@37 400 fprintf(stderr, "o(%d): \"%s\"\n", (int)i, output[i].c_str());
nuclear@37 401 }
nuclear@37 402 }