| 1 | // This file is part of SmallBASIC |
| 2 | // |
| 3 | // Copyright(C) 2001-2019 Chris Warren-Smith. |
| 4 | // |
| 5 | // This program is distributed under the terms of the GPL v2.0 or later |
| 6 | // Download the GNU Public License (GPL) from www.gnu.org |
| 7 | // |
| 8 | |
| 9 | #include <config.h> |
| 10 | #include <stdint.h> |
| 11 | #include <FL/Fl_Rect.H> |
| 12 | #include "platform/fltk/BasicEditor.h" |
| 13 | #include "platform/fltk/kwp.h" |
| 14 | #include "platform/fltk/Profile.h" |
| 15 | |
| 16 | using namespace strlib; |
| 17 | |
| 18 | Fl_Color defaultColor[] = { |
| 19 | FL_BLACK, // A - Plain |
| 20 | fl_rgb_color(0, 128, 0), // B - Comments |
| 21 | fl_rgb_color(0, 0, 192), // C - Strings |
| 22 | fl_rgb_color(192, 0, 0), // D - code_keywords |
| 23 | fl_rgb_color(128, 128, 0), // E - code_functions |
| 24 | fl_rgb_color(0, 128, 128), // F - code_procedures |
| 25 | fl_rgb_color(128, 0, 128), // G - Find matches |
| 26 | fl_rgb_color(0, 128, 0), // H - Italic Comments ' |
| 27 | fl_rgb_color(0, 128, 128), // I - Numbers |
| 28 | fl_rgb_color(128, 128, 64), // J - Operators |
| 29 | }; |
| 30 | |
| 31 | Fl_Text_Display::Style_Table_Entry styletable[] = { |
| 32 | { defaultColor[0], FL_COURIER, 12}, // A - Plain |
| 33 | { defaultColor[1], FL_COURIER, 12}, // B - Comments |
| 34 | { defaultColor[2], FL_COURIER, 12}, // C - Strings |
| 35 | { defaultColor[3], FL_COURIER, 12}, // D - code_keywords |
| 36 | { defaultColor[4], FL_COURIER, 12}, // E - code_functions |
| 37 | { defaultColor[5], FL_COURIER, 12}, // F - code_procedures |
| 38 | { defaultColor[6], FL_COURIER, 12}, // G - Find matches |
| 39 | { defaultColor[7], FL_COURIER_ITALIC, 12}, // H - Italic Comments |
| 40 | { defaultColor[8], FL_COURIER, 12}, // I - Numbers |
| 41 | { defaultColor[9], FL_COURIER, 12}, // J - Operators |
| 42 | { FL_BLUE, FL_COURIER, 12}, // K - Selection Background |
| 43 | { FL_WHITE, FL_COURIER, 12}, // L - Background |
| 44 | }; |
| 45 | |
| 46 | #define PLAIN 'A' |
| 47 | #define 'B' |
| 48 | #define STRINGS 'C' |
| 49 | #define KEYWORDS 'D' |
| 50 | #define FUNCTIONS 'E' |
| 51 | #define PROCEDURES 'F' |
| 52 | #define FINDMATCH 'G' |
| 53 | #define 'H' |
| 54 | #define DIGITS 'I' |
| 55 | #define OPERATORS 'J' |
| 56 | |
| 57 | /** |
| 58 | * return whether the character is a valid variable symbol |
| 59 | */ |
| 60 | bool isvar(int c) { |
| 61 | return (isalnum(c) || c == '_'); |
| 62 | } |
| 63 | |
| 64 | /** |
| 65 | * Compare two keywords |
| 66 | */ |
| 67 | int compare_keywords(const void *a, const void *b) { |
| 68 | return (strcasecmp(*((const char **)a), *((const char **)b))); |
| 69 | } |
| 70 | |
| 71 | /** |
| 72 | * Update unfinished styles. |
| 73 | */ |
| 74 | void style_unfinished_cb(int, void *) { |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * Update the style buffer |
| 79 | */ |
| 80 | void style_update_cb(int pos, // I - Position of update |
| 81 | int nInserted, // I - Number of inserted chars |
| 82 | int nDeleted, // I - Number of deleted chars |
| 83 | int /* nRestyled */ , // I - Number of restyled chars |
| 84 | const char * /* deletedText */ , // I - Text that was deleted |
| 85 | void *cbArg) { // I - Callback data |
| 86 | BasicEditor *editor = (BasicEditor *) cbArg; |
| 87 | Fl_Text_Buffer *stylebuf = editor->_stylebuf; |
| 88 | Fl_Text_Buffer *textbuf = editor->_textbuf; |
| 89 | |
| 90 | // if this is just a selection change, just unselect the style buffer |
| 91 | if (nInserted == 0 && nDeleted == 0) { |
| 92 | stylebuf->unselect(); |
| 93 | return; |
| 94 | } |
| 95 | // track changes in the text buffer |
| 96 | if (nInserted > 0) { |
| 97 | // insert characters into the style buffer |
| 98 | char *stylex = new char[nInserted + 1]; |
| 99 | memset(stylex, PLAIN, nInserted); |
| 100 | stylex[nInserted] = '\0'; |
| 101 | stylebuf->replace(pos, pos + nDeleted, stylex); |
| 102 | delete[]stylex; |
| 103 | } else { |
| 104 | // just delete characters in the style buffer |
| 105 | stylebuf->remove(pos, pos + nDeleted); |
| 106 | } |
| 107 | |
| 108 | // Select the area that was just updated to avoid unnecessary callbacks |
| 109 | stylebuf->select(pos, pos + nInserted - nDeleted); |
| 110 | |
| 111 | // re-parse the changed region; we do this by parsing from the |
| 112 | // beginning of the line of the changed region to the end of |
| 113 | // the line of the changed region Then we check the last |
| 114 | // style character and keep updating if we have a multi-line |
| 115 | // comment character |
| 116 | if (nInserted > 0) { |
| 117 | int start = textbuf->line_start(pos); |
| 118 | int end = textbuf->line_end(pos + nInserted); |
| 119 | char *text_range = textbuf->text_range(start, end); |
| 120 | char *style_range = stylebuf->text_range(start, end); |
| 121 | int last = style_range[end - start - 1]; |
| 122 | |
| 123 | editor->styleParse(text_range, style_range, end - start); |
| 124 | stylebuf->replace(start, end, style_range); |
| 125 | editor->redisplay_range(start, end); |
| 126 | |
| 127 | if (last != style_range[end - start - 1]) { |
| 128 | // the last character on the line changed styles, |
| 129 | // so reparse the remainder of the buffer |
| 130 | free(text_range); |
| 131 | free(style_range); |
| 132 | end = textbuf->length(); |
| 133 | text_range = textbuf->text_range(start, end); |
| 134 | style_range = stylebuf->text_range(start, end); |
| 135 | editor->styleParse(text_range, style_range, end - start); |
| 136 | stylebuf->replace(start, end, style_range); |
| 137 | editor->redisplay_range(start, end); |
| 138 | } |
| 139 | free(text_range); |
| 140 | free(style_range); |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | //--BasicEditor------------------------------------------------------------------ |
| 145 | |
| 146 | BasicEditor::BasicEditor(int x, int y, int w, int h, StatusBar *status) : |
| 147 | Fl_Text_Editor(x, y, w, h), |
| 148 | _readonly(false), |
| 149 | _status(status) { |
| 150 | |
| 151 | const char *s = getenv("INDENT_LEVEL" ); |
| 152 | _indentLevel = (s && s[0] ? atoi(s) : 2); |
| 153 | _matchingBrace = -1; |
| 154 | _textbuf = new Fl_Text_Buffer(); |
| 155 | _stylebuf = new Fl_Text_Buffer(); |
| 156 | _search[0] = 0; |
| 157 | highlight_data(_stylebuf, styletable, |
| 158 | sizeof(styletable) / sizeof(styletable[0]), |
| 159 | PLAIN, style_unfinished_cb, 0); |
| 160 | _textbuf->add_modify_callback(style_update_cb, this); |
| 161 | buffer(_textbuf); |
| 162 | } |
| 163 | |
| 164 | BasicEditor::~BasicEditor() { |
| 165 | buffer(nullptr); |
| 166 | delete _textbuf; |
| 167 | delete _stylebuf; |
| 168 | } |
| 169 | |
| 170 | /** |
| 171 | * Parse text and produce style data. |
| 172 | */ |
| 173 | void BasicEditor::styleParse(const char *text, char *style, int length) { |
| 174 | char current = PLAIN; |
| 175 | int last = 0; // prev char was alpha-num |
| 176 | char buf[1024]; |
| 177 | char *bufptr; |
| 178 | const char *temp; |
| 179 | int searchLen = strlen(_search); |
| 180 | |
| 181 | for (int index = 0; length > 0; length--, text++, index++) { |
| 182 | if (current == PLAIN) { |
| 183 | // check for directives, comments, strings, and keywords |
| 184 | if ((*text == '#' && (index == 0 || *(text - 1) == 0 || *(text - 1) == 10)) || |
| 185 | (strncasecmp(text, "rem" , 3) == 0 && text[3] == ' ') || *text == '\'') { |
| 186 | // basic comment |
| 187 | current = COMMENTS; |
| 188 | for (; length > 0 && *text != '\n'; length--, text++) { |
| 189 | if (*text == ';') { |
| 190 | current = ITCOMMENTS; |
| 191 | } |
| 192 | *style++ = current; |
| 193 | } |
| 194 | if (length == 0) { |
| 195 | break; |
| 196 | } |
| 197 | } else if (strncmp(text, "\\\"" , 2) == 0) { |
| 198 | // quoted quote |
| 199 | *style++ = current; |
| 200 | *style++ = current; |
| 201 | text++; |
| 202 | length--; |
| 203 | continue; |
| 204 | } else if (*text == '\"') { |
| 205 | current = STRINGS; |
| 206 | } else if (!last) { |
| 207 | // begin keyword/number search at non-alnum boundary |
| 208 | |
| 209 | // test for digit sequence |
| 210 | if (isdigit(*text)) { |
| 211 | *style++ = DIGITS; |
| 212 | if (*text == '0' && *(text + 1) == 'x') { |
| 213 | // hex number |
| 214 | *style++ = DIGITS; |
| 215 | text++; |
| 216 | length--; |
| 217 | } |
| 218 | while (*text && (*(text + 1) == '.' || isdigit(*(text + 1)))) { |
| 219 | *style++ = DIGITS; |
| 220 | text++; |
| 221 | length--; |
| 222 | } |
| 223 | continue; |
| 224 | } |
| 225 | // test for a keyword |
| 226 | temp = text; |
| 227 | bufptr = buf; |
| 228 | while (*temp != 0 && *temp != ' ' && |
| 229 | *temp != '\n' && *temp != '\r' && *temp != '"' && |
| 230 | *temp != '(' && *temp != ')' && *temp != '=' && bufptr < (buf + sizeof(buf) - 1)) { |
| 231 | *bufptr++ = tolower(*temp++); |
| 232 | } |
| 233 | |
| 234 | *bufptr = '\0'; |
| 235 | bufptr = buf; |
| 236 | |
| 237 | if (searchLen > 0) { |
| 238 | const char *sfind = strstr(bufptr, _search); |
| 239 | // find text match |
| 240 | if (sfind != 0) { |
| 241 | int offset = sfind - bufptr; |
| 242 | style += offset; |
| 243 | text += offset; |
| 244 | length -= offset; |
| 245 | for (int i = 0; i < searchLen && text < temp; i++) { |
| 246 | *style++ = FINDMATCH; |
| 247 | text++; |
| 248 | length--; |
| 249 | } |
| 250 | text--; |
| 251 | length++; |
| 252 | last = 1; |
| 253 | continue; |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | if (bsearch(&bufptr, code_keywords, code_keywords_len, sizeof(code_keywords[0]), compare_keywords)) { |
| 258 | while (text < temp) { |
| 259 | *style++ = KEYWORDS; |
| 260 | text++; |
| 261 | length--; |
| 262 | } |
| 263 | text--; |
| 264 | length++; |
| 265 | last = 1; |
| 266 | continue; |
| 267 | } else if (bsearch(&bufptr, code_functions, code_functions_len, |
| 268 | sizeof(code_functions[0]), compare_keywords)) { |
| 269 | while (text < temp) { |
| 270 | *style++ = FUNCTIONS; |
| 271 | text++; |
| 272 | length--; |
| 273 | } |
| 274 | text--; |
| 275 | length++; |
| 276 | last = 1; |
| 277 | continue; |
| 278 | } else if (bsearch(&bufptr, code_procedures, code_procedures_len, |
| 279 | sizeof(code_procedures[0]), compare_keywords)) { |
| 280 | while (text < temp) { |
| 281 | *style++ = PROCEDURES; |
| 282 | text++; |
| 283 | length--; |
| 284 | } |
| 285 | text--; |
| 286 | length++; |
| 287 | last = 1; |
| 288 | continue; |
| 289 | } |
| 290 | } |
| 291 | } else if (current == STRINGS) { |
| 292 | // continuing in string |
| 293 | if (strncmp(text, "\\\"" , 2) == 0) { |
| 294 | // quoted end quote |
| 295 | *style++ = current; |
| 296 | *style++ = current; |
| 297 | text++; |
| 298 | length--; |
| 299 | continue; |
| 300 | } else if (*text == '\"') { |
| 301 | // End quote |
| 302 | *style++ = current; |
| 303 | current = PLAIN; |
| 304 | continue; |
| 305 | } |
| 306 | } |
| 307 | // copy style info |
| 308 | *style++ = current; |
| 309 | last = isvar(*text) || *text == '.'; |
| 310 | |
| 311 | if (*text == '\n') { |
| 312 | current = PLAIN; // basic lines do not continue |
| 313 | } |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | /** |
| 318 | * handler for the style change event |
| 319 | */ |
| 320 | void BasicEditor::styleChanged() { |
| 321 | _textbuf->select(0, _textbuf->length()); |
| 322 | _textbuf->select(0, 0); |
| 323 | damage(FL_DAMAGE_ALL); |
| 324 | } |
| 325 | |
| 326 | /** |
| 327 | * display the editor buffer |
| 328 | */ |
| 329 | void BasicEditor::draw() { |
| 330 | Fl_Text_Editor::draw(); |
| 331 | if (_matchingBrace != -1) { |
| 332 | // highlight the matching brace |
| 333 | int X, Y; |
| 334 | int cursor = cursor_style(); |
| 335 | cursor_style(BLOCK_CURSOR); |
| 336 | if (position_to_xy(_matchingBrace, &X, &Y)) { |
| 337 | draw_cursor(X, Y); |
| 338 | } |
| 339 | cursor_style(cursor); |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | /** |
| 344 | * returns the indent position level |
| 345 | */ |
| 346 | unsigned BasicEditor::getIndent(char *spaces, int len, int pos) { |
| 347 | // count the indent level and find the start of text |
| 348 | char *buf = buffer()->line_text(pos); |
| 349 | int i = 0; |
| 350 | while (buf && buf[i] == ' ' && i < len) { |
| 351 | spaces[i] = buf[i]; |
| 352 | i++; |
| 353 | } |
| 354 | |
| 355 | if (strncasecmp(buf + i, "while " , 6) == 0 || |
| 356 | strncasecmp(buf + i, "if " , 3) == 0 || |
| 357 | strncasecmp(buf + i, "elseif " , 7) == 0 || |
| 358 | strncasecmp(buf + i, "elif " , 5) == 0 || |
| 359 | strncasecmp(buf + i, "else" , 4) == 0 || |
| 360 | strncasecmp(buf + i, "repeat" , 6) == 0 || |
| 361 | strncasecmp(buf + i, "for " , 4) == 0 || |
| 362 | strncasecmp(buf + i, "select " , 7) == 0 || |
| 363 | strncasecmp(buf + i, "case " , 5) == 0 || |
| 364 | strncasecmp(buf + i, "sub " , 4) == 0 || |
| 365 | strncasecmp(buf + i, "func " , 5) == 0) { |
| 366 | |
| 367 | // handle if-then-blah on same line |
| 368 | if (strncasecmp(buf + i, "if " , 3) == 0) { |
| 369 | // find the end of line index |
| 370 | int j = i + 4; |
| 371 | while (buf[j] != 0 && buf[j] != '\n') { |
| 372 | // line also 'ends' at start of comments |
| 373 | if (strncasecmp(buf + j, "rem" , 3) == 0 || buf[j] == '\'') { |
| 374 | break; |
| 375 | } |
| 376 | j++; |
| 377 | } |
| 378 | // right trim trailing spaces |
| 379 | while (buf[j - 1] == ' ' && j > i) { |
| 380 | j--; |
| 381 | } |
| 382 | if (strncasecmp(buf + j - 4, "then" , 4) != 0) { |
| 383 | // 'then' is not final text on line |
| 384 | spaces[i] = 0; |
| 385 | return i; |
| 386 | } |
| 387 | } |
| 388 | // indent new line |
| 389 | for (int j = 0; j < _indentLevel; j++, i++) { |
| 390 | spaces[i] = ' '; |
| 391 | } |
| 392 | } |
| 393 | spaces[i] = 0; |
| 394 | free((void *)buf); |
| 395 | return i; |
| 396 | } |
| 397 | |
| 398 | /** |
| 399 | * handler for the TAB character |
| 400 | */ |
| 401 | void BasicEditor::handleTab() { |
| 402 | char spaces[250]; |
| 403 | int indent; |
| 404 | |
| 405 | // get the desired indent based on the previous line |
| 406 | int lineStart = buffer()->line_start(insert_position()); |
| 407 | int prevLineStart = buffer()->line_start(lineStart - 1); |
| 408 | |
| 409 | if (prevLineStart && prevLineStart + 1 == lineStart) { |
| 410 | // allows for a single blank line between statments |
| 411 | prevLineStart = buffer()->line_start(prevLineStart - 1); |
| 412 | } |
| 413 | // note - spaces not used in this context |
| 414 | indent = prevLineStart == 0 ? 0 : getIndent(spaces, sizeof(spaces), prevLineStart); |
| 415 | |
| 416 | // get the current lines indent |
| 417 | char *buf = buffer()->line_text(lineStart); |
| 418 | int curIndent = 0; |
| 419 | while (buf && buf[curIndent] == ' ') { |
| 420 | curIndent++; |
| 421 | } |
| 422 | |
| 423 | // adjust indent for closure statements |
| 424 | if (strncasecmp(buf + curIndent, "wend" , 4) == 0 || |
| 425 | strncasecmp(buf + curIndent, "fi" , 2) == 0 || |
| 426 | strncasecmp(buf + curIndent, "endif" , 5) == 0 || |
| 427 | strncasecmp(buf + curIndent, "elseif " , 7) == 0 || |
| 428 | strncasecmp(buf + curIndent, "elif " , 5) == 0 || |
| 429 | strncasecmp(buf + curIndent, "else" , 4) == 0 || |
| 430 | strncasecmp(buf + curIndent, "next" , 4) == 0 || |
| 431 | strncasecmp(buf + curIndent, "case" , 4) == 0 || |
| 432 | strncasecmp(buf + curIndent, "end" , 3) == 0 || |
| 433 | strncasecmp(buf + curIndent, "until " , 6) == 0) { |
| 434 | if (indent >= _indentLevel) { |
| 435 | indent -= _indentLevel; |
| 436 | } |
| 437 | } |
| 438 | if (curIndent < indent) { |
| 439 | // insert additional spaces |
| 440 | int len = indent - curIndent; |
| 441 | if (len > (int)sizeof(spaces) - 1) { |
| 442 | len = (int)sizeof(spaces) - 1; |
| 443 | } |
| 444 | memset(spaces, ' ', len); |
| 445 | spaces[len] = 0; |
| 446 | buffer()->insert(lineStart, spaces); |
| 447 | if (insert_position() - lineStart < indent) { |
| 448 | // jump cursor to start of text |
| 449 | insert_position(lineStart + indent); |
| 450 | } else { |
| 451 | // move cursor along with text movement, staying on same line |
| 452 | int maxpos = buffer()->line_end(lineStart); |
| 453 | if (insert_position() + len <= maxpos) { |
| 454 | insert_position(insert_position() + len); |
| 455 | } |
| 456 | } |
| 457 | } else if (curIndent > indent) { |
| 458 | // remove excess spaces |
| 459 | buffer()->remove(lineStart, lineStart + (curIndent - indent)); |
| 460 | } else { |
| 461 | // already have ideal indent - soft-tab to indent |
| 462 | insert_position(lineStart + indent); |
| 463 | } |
| 464 | free((void *)buf); |
| 465 | } |
| 466 | |
| 467 | /** |
| 468 | * sets the current display font |
| 469 | */ |
| 470 | void BasicEditor::setFont(Fl_Font font) { |
| 471 | if (font) { |
| 472 | int len = sizeof(styletable) / sizeof(styletable[0]); |
| 473 | for (int i = 0; i < len; i++) { |
| 474 | styletable[i].font = font; |
| 475 | } |
| 476 | styleChanged(); |
| 477 | } |
| 478 | } |
| 479 | |
| 480 | /** |
| 481 | * sets the current font size |
| 482 | */ |
| 483 | void BasicEditor::setFontSize(int size) { |
| 484 | int len = sizeof(styletable) / sizeof(styletable[0]); |
| 485 | for (int i = 0; i < len; i++) { |
| 486 | styletable[i].size = size; |
| 487 | } |
| 488 | styleChanged(); |
| 489 | } |
| 490 | |
| 491 | /** |
| 492 | * display the matching brace |
| 493 | */ |
| 494 | void BasicEditor::showMatchingBrace() { |
| 495 | char cursorChar = buffer()->char_at(insert_position() - 1); |
| 496 | char cursorMatch = 0; |
| 497 | int pair = -1; |
| 498 | int iter = -1; |
| 499 | int pos = insert_position() - 2; |
| 500 | |
| 501 | switch (cursorChar) { |
| 502 | case ']': |
| 503 | cursorMatch = '['; |
| 504 | break; |
| 505 | case ')': |
| 506 | cursorMatch = '('; |
| 507 | break; |
| 508 | case '(': |
| 509 | cursorMatch = ')'; |
| 510 | pos = insert_position(); |
| 511 | iter = 1; |
| 512 | break; |
| 513 | case '[': |
| 514 | cursorMatch = ']'; |
| 515 | iter = 1; |
| 516 | pos = insert_position(); |
| 517 | break; |
| 518 | } |
| 519 | if (cursorMatch != -0) { |
| 520 | // scan for matching opening on the same line |
| 521 | int level = 1; |
| 522 | int len = buffer()->length(); |
| 523 | int gap = 0; |
| 524 | while (pos > 0 && pos < len) { |
| 525 | char nextChar = buffer()->char_at(pos); |
| 526 | if (nextChar == 0 || nextChar == '\n') { |
| 527 | break; |
| 528 | } |
| 529 | if (nextChar == cursorChar) { |
| 530 | level++; // nested char |
| 531 | } else if (nextChar == cursorMatch) { |
| 532 | level--; |
| 533 | if (level == 0) { |
| 534 | // found matching char at pos |
| 535 | if (gap > 1) { |
| 536 | pair = pos; |
| 537 | } |
| 538 | break; |
| 539 | } |
| 540 | } |
| 541 | pos += iter; |
| 542 | gap++; |
| 543 | } |
| 544 | } |
| 545 | |
| 546 | if (_matchingBrace != -1) { |
| 547 | int lineStart = buffer()->line_start(_matchingBrace); |
| 548 | int lineEnd = buffer()->line_end(_matchingBrace); |
| 549 | redisplay_range(lineStart, lineEnd); |
| 550 | _matchingBrace = -1; |
| 551 | } |
| 552 | if (pair != -1) { |
| 553 | redisplay_range(pair, pair); |
| 554 | _matchingBrace = pair; |
| 555 | } |
| 556 | } |
| 557 | |
| 558 | /** |
| 559 | * highlight the given search text |
| 560 | */ |
| 561 | void BasicEditor::showFindText(const char *find) { |
| 562 | // copy lowercase search string for high-lighting |
| 563 | strcpy(_search, find); |
| 564 | int findLen = strlen(_search); |
| 565 | |
| 566 | for (int i = 0; i < findLen; i++) { |
| 567 | _search[i] = tolower(_search[i]); |
| 568 | } |
| 569 | |
| 570 | style_update_cb(0, _textbuf->length(), _textbuf->length(), 0, 0, this); |
| 571 | } |
| 572 | |
| 573 | /** |
| 574 | * FLTK event handler |
| 575 | */ |
| 576 | int BasicEditor::handle(int e) { |
| 577 | int cursor_pos = insert_position(); |
| 578 | bool navigateKey = false; |
| 579 | |
| 580 | switch (Fl::event_key()) { |
| 581 | case FL_Home: |
| 582 | case FL_Left: |
| 583 | case FL_Up: |
| 584 | case FL_Right: |
| 585 | case FL_Down: |
| 586 | case FL_Page_Up: |
| 587 | case FL_Page_Down: |
| 588 | case FL_End: |
| 589 | navigateKey = true; |
| 590 | } |
| 591 | |
| 592 | if (_readonly && ((e == FL_KEYBOARD && !navigateKey) || e == FL_PASTE)) { |
| 593 | // prevent buffer modification when in readonly state |
| 594 | return 0; |
| 595 | } |
| 596 | |
| 597 | if (e == FL_KEYBOARD && Fl::event_key() == FL_Tab) { |
| 598 | if (Fl::event_state(FL_CTRL)) { |
| 599 | // pass ctrl+key to parent |
| 600 | return 0; |
| 601 | } |
| 602 | handleTab(); |
| 603 | return 1; // skip default handler |
| 604 | } |
| 605 | // call default handler then process keys |
| 606 | int rtn = Fl_Text_Editor::handle(e); |
| 607 | switch (e) { |
| 608 | case FL_KEYBOARD: |
| 609 | if (Fl::event_key() == FL_Enter) { |
| 610 | char spaces[250]; |
| 611 | int indent = getIndent(spaces, sizeof(spaces), cursor_pos); |
| 612 | if (indent) { |
| 613 | buffer()->insert(insert_position(), spaces); |
| 614 | insert_position(insert_position() + indent); |
| 615 | damage(FL_DAMAGE_ALL); |
| 616 | } |
| 617 | } |
| 618 | // fallthru to show row-col |
| 619 | case FL_RELEASE: |
| 620 | showMatchingBrace(); |
| 621 | showRowCol(); |
| 622 | break; |
| 623 | } |
| 624 | |
| 625 | return rtn; |
| 626 | } |
| 627 | |
| 628 | /** |
| 629 | * displays the current row and col position |
| 630 | */ |
| 631 | void BasicEditor::showRowCol() { |
| 632 | int row = -1; |
| 633 | int col = 0; |
| 634 | |
| 635 | if (!position_to_linecol(insert_position(), &row, &col)) { |
| 636 | // This is a workaround for a bug in the FLTK TextDisplay widget |
| 637 | // where linewrapping causes a mis-calculation of line offsets which |
| 638 | // sometimes prevents the display of the last few lines of text. |
| 639 | insert_position(0); |
| 640 | scroll(0, 0); |
| 641 | insert_position(buffer()->length()); |
| 642 | scroll(count_lines(0, buffer()->length(), 1), 0); |
| 643 | position_to_linecol(insert_position(), &row, &col); |
| 644 | } |
| 645 | |
| 646 | _status->setRowCol(row, col + 1); |
| 647 | } |
| 648 | |
| 649 | /** |
| 650 | * sets the cursor to the given line number |
| 651 | */ |
| 652 | void BasicEditor::gotoLine(int line) { |
| 653 | int numLines = buffer()->count_lines(0, buffer()->length()); |
| 654 | if (line < 1) { |
| 655 | line = 1; |
| 656 | } else if (line > numLines) { |
| 657 | line = numLines; |
| 658 | } |
| 659 | int pos = buffer()->skip_lines(0, line - 1); // find pos at line-1 |
| 660 | insert_position(buffer()->line_start(pos)); // insert at column 0 |
| 661 | show_insert_position(); |
| 662 | _status->setRowCol(line, 1); |
| 663 | scroll(line, hor_offset()); |
| 664 | } |
| 665 | |
| 666 | /** |
| 667 | * returns where text selection starts |
| 668 | */ |
| 669 | void BasicEditor::getSelStartRowCol(int *row, int *col) { |
| 670 | int start = buffer()->primary_selection()->start(); |
| 671 | int end = buffer()->primary_selection()->end(); |
| 672 | if (start == end) { |
| 673 | *row = -1; |
| 674 | *col = -1; |
| 675 | } else { |
| 676 | position_to_linecol(start, row, col); |
| 677 | } |
| 678 | } |
| 679 | |
| 680 | /** |
| 681 | * returns where text selection ends |
| 682 | */ |
| 683 | void BasicEditor::getSelEndRowCol(int *row, int *col) { |
| 684 | int start = buffer()->primary_selection()->start(); |
| 685 | int end = buffer()->primary_selection()->end(); |
| 686 | if (start == end) { |
| 687 | *row = -1; |
| 688 | *col = -1; |
| 689 | } else { |
| 690 | position_to_linecol(end, row, col); |
| 691 | } |
| 692 | } |
| 693 | |
| 694 | /** |
| 695 | * return the selected text and its coordinate rectangle |
| 696 | */ |
| 697 | char *BasicEditor::getSelection(Fl_Rect *rc) { |
| 698 | char *result = 0; |
| 699 | if (!_readonly) { |
| 700 | int x1, y1, x2, y2, start, end; |
| 701 | |
| 702 | if (_textbuf->selected()) { |
| 703 | _textbuf->selection_position(&start, &end); |
| 704 | } else { |
| 705 | int pos = insert_position(); |
| 706 | if (isvar(_textbuf->char_at(pos))) { |
| 707 | start = _textbuf->word_start(pos); |
| 708 | end = _textbuf->word_end(pos); |
| 709 | } else { |
| 710 | start = end = 0; |
| 711 | } |
| 712 | } |
| 713 | |
| 714 | if (start != end) { |
| 715 | position_to_xy(start, &x1, &y1); |
| 716 | position_to_xy(end, &x2, &y2); |
| 717 | |
| 718 | rc->x(x1); |
| 719 | rc->y(y1); |
| 720 | rc->w(x2 - x1); |
| 721 | rc->h(maxSize()); |
| 722 | result = _textbuf->text_range(start, end); |
| 723 | } |
| 724 | } |
| 725 | return result; |
| 726 | } |
| 727 | |
| 728 | /** |
| 729 | * returns the current font size |
| 730 | */ |
| 731 | int BasicEditor::getFontSize() { |
| 732 | return (int)styletable[0].size; |
| 733 | } |
| 734 | |
| 735 | /** |
| 736 | * returns the current font face name |
| 737 | */ |
| 738 | Fl_Font BasicEditor::getFont() { |
| 739 | return styletable[0].font; |
| 740 | } |
| 741 | |
| 742 | /** |
| 743 | * returns the BASIC keyword list |
| 744 | */ |
| 745 | void BasicEditor::getKeywords(strlib::List<String *> &keywords) { |
| 746 | for (int i = 0; i < code_keywords_len; i++) { |
| 747 | keywords.add(new String(code_keywords[i])); |
| 748 | } |
| 749 | |
| 750 | for (int i = 0; i < code_functions_len; i++) { |
| 751 | keywords.add(new String(code_functions[i])); |
| 752 | } |
| 753 | |
| 754 | for (int i = 0; i < code_procedures_len; i++) { |
| 755 | keywords.add(new String(code_procedures[i])); |
| 756 | } |
| 757 | } |
| 758 | |
| 759 | /** |
| 760 | * returns the row and col position for the current cursor position |
| 761 | */ |
| 762 | void BasicEditor::getRowCol(int *row, int *col) { |
| 763 | position_to_linecol(insert_position(), row, col); |
| 764 | } |
| 765 | |
| 766 | /** |
| 767 | * find text within the editor buffer |
| 768 | */ |
| 769 | bool BasicEditor::findText(const char *find, bool forward, bool updatePos) { |
| 770 | showFindText(find); |
| 771 | |
| 772 | bool found = false; |
| 773 | if (find != 0 && find[0] != 0) { |
| 774 | int pos = insert_position(); |
| 775 | found = forward ? _textbuf->search_forward(pos, _search, &pos) : |
| 776 | _textbuf->search_backward(pos - strlen(find), _search, &pos); |
| 777 | if (found && updatePos) { |
| 778 | _textbuf->select(pos, pos + strlen(_search)); |
| 779 | insert_position(pos + strlen(_search)); |
| 780 | show_insert_position(); |
| 781 | } |
| 782 | } |
| 783 | return found; |
| 784 | } |
| 785 | |
| 786 | |