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 | |