fltk 1.3.0rc3
About: FLTK (Fast Light Tool Kit) is a cross-platform C++ GUI toolkit for UNIX/Linux (X11), Microsoft Windows, and MacOS X. Release candidate.
  SfR Fresh Dox: fltk-1.3.0rc3-source.tar.gz ("inofficial" and yet experimental doxygen-generated source code documentation)  

CodeEditor.cxx

Go to the documentation of this file.
00001 //
00002 // "$Id: CodeEditor.cxx 7903 2010-11-28 21:06:39Z matt $"
00003 //
00004 // Code editor widget for the Fast Light Tool Kit (FLTK).
00005 //
00006 // Copyright 1998-2010 by Bill Spitzak and others.
00007 //
00008 // This library is free software; you can redistribute it and/or
00009 // modify it under the terms of the GNU Library General Public
00010 // License as published by the Free Software Foundation; either
00011 // version 2 of the License, or (at your option) any later version.
00012 //
00013 // This library is distributed in the hope that it will be useful,
00014 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00015 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016 // Library General Public License for more details.
00017 //
00018 // You should have received a copy of the GNU Library General Public
00019 // License along with this library; if not, write to the Free Software
00020 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
00021 // USA.
00022 //
00023 // Please report all bugs and problems on the following page:
00024 //
00025 //     http://www.fltk.org/str.php
00026 //
00027 
00028 //
00029 // Include necessary headers...
00030 //
00031 
00032 #include <stdio.h>
00033 #include <stdlib.h>
00034 #include <string.h>
00035 #include <ctype.h>
00036 #include "CodeEditor.h"
00037 
00038 
00039 Fl_Text_Display::Style_Table_Entry CodeEditor::
00040                 styletable[] = {        // Style table
00041                   { FL_FOREGROUND_COLOR, FL_COURIER,        11 }, // A - Plain
00042                   { FL_DARK_GREEN,       FL_COURIER_ITALIC, 11 }, // B - Line comments
00043                   { FL_DARK_GREEN,       FL_COURIER_ITALIC, 11 }, // C - Block comments
00044                   { FL_BLUE,             FL_COURIER,        11 }, // D - Strings
00045                   { FL_DARK_RED,         FL_COURIER,        11 }, // E - Directives
00046                   { FL_DARK_RED,         FL_COURIER_BOLD,   11 }, // F - Types
00047                   { FL_BLUE,             FL_COURIER_BOLD,   11 }  // G - Keywords
00048                 };
00049 const char * const CodeEditor::
00050                 code_keywords[] = {     // Sorted list of C/C++ keywords...
00051                   "and",
00052                   "and_eq",
00053                   "asm",
00054                   "bitand",
00055                   "bitor",
00056                   "break",
00057                   "case",
00058                   "catch",
00059                   "compl",
00060                   "continue",
00061                   "default",
00062                   "delete",
00063                   "do",
00064                   "else",
00065                   "false",
00066                   "for",
00067                   "goto",
00068                   "if",
00069                   "new",
00070                   "not",
00071                   "not_eq",
00072                   "operator",
00073                   "or",
00074                   "or_eq",
00075                   "return",
00076                   "switch",
00077                   "template",
00078                   "this",
00079                   "throw",
00080                   "true",
00081                   "try",
00082                   "while",
00083                   "xor",
00084                   "xor_eq"
00085                 };
00086 const char * const CodeEditor::
00087                 code_types[] = {        // Sorted list of C/C++ types...
00088                   "auto",
00089                   "bool",
00090                   "char",
00091                   "class",
00092                   "const",
00093                   "const_cast",
00094                   "double",
00095                   "dynamic_cast",
00096                   "enum",
00097                   "explicit",
00098                   "extern",
00099                   "float",
00100                   "friend",
00101                   "inline",
00102                   "int",
00103                   "long",
00104                   "mutable",
00105                   "namespace",
00106                   "private",
00107                   "protected",
00108                   "public",
00109                   "register",
00110                   "short",
00111                   "signed",
00112                   "sizeof",
00113                   "static",
00114                   "static_cast",
00115                   "struct",
00116                   "template",
00117                   "typedef",
00118                   "typename",
00119                   "union",
00120                   "unsigned",
00121                   "virtual",
00122                   "void",
00123                   "volatile"
00124                 };
00125 
00126 
00127 // 'compare_keywords()' - Compare two keywords...
00128 int CodeEditor::compare_keywords(const void *a, const void *b) {
00129   return (strcmp(*((const char **)a), *((const char **)b)));
00130 }
00131 
00132 // 'style_parse()' - Parse text and produce style data.
00133 void CodeEditor::style_parse(const char *text, char *style, int length) {
00134   char          current;
00135   int           col;
00136   int           last;
00137   char          buf[255],
00138                 *bufptr;
00139   const char    *temp;
00140 
00141   // Style letters:
00142   //
00143   // A - Plain
00144   // B - Line comments
00145   // C - Block comments
00146   // D - Strings
00147   // E - Directives
00148   // F - Types
00149   // G - Keywords
00150 
00151   for (current = *style, col = 0, last = 0; length > 0; length --, text ++) {
00152     if (current == 'B' || current == 'F' || current == 'G') current = 'A';
00153     if (current == 'A') {
00154       // Check for directives, comments, strings, and keywords...
00155       if (col == 0 && *text == '#') {
00156         // Set style to directive
00157         current = 'E';
00158       } else if (strncmp(text, "//", 2) == 0) {
00159         current = 'B';
00160         for (; length > 0 && *text != '\n'; length --, text ++) *style++ = 'B';
00161 
00162         if (length == 0) break;
00163       } else if (strncmp(text, "/*", 2) == 0) {
00164         current = 'C';
00165       } else if (strncmp(text, "\\\"", 2) == 0) {
00166         // Quoted quote...
00167         *style++ = current;
00168         *style++ = current;
00169         text ++;
00170         length --;
00171         col += 2;
00172         continue;
00173       } else if (*text == '\"') {
00174         current = 'D';
00175       } else if (!last && (islower(*text) || *text == '_')) {
00176         // Might be a keyword...
00177         for (temp = text, bufptr = buf;
00178              (islower(*temp) || *temp == '_') && bufptr < (buf + sizeof(buf) - 1);
00179              *bufptr++ = *temp++);
00180 
00181         if (!islower(*temp) && *temp != '_') {
00182           *bufptr = '\0';
00183 
00184           bufptr = buf;
00185 
00186           if (bsearch(&bufptr, code_types,
00187                       sizeof(code_types) / sizeof(code_types[0]),
00188                       sizeof(code_types[0]), compare_keywords)) {
00189             while (text < temp) {
00190               *style++ = 'F';
00191               text ++;
00192               length --;
00193               col ++;
00194             }
00195 
00196             text --;
00197             length ++;
00198             last = 1;
00199             continue;
00200           } else if (bsearch(&bufptr, code_keywords,
00201                              sizeof(code_keywords) / sizeof(code_keywords[0]),
00202                              sizeof(code_keywords[0]), compare_keywords)) {
00203             while (text < temp) {
00204               *style++ = 'G';
00205               text ++;
00206               length --;
00207               col ++;
00208             }
00209 
00210             text --;
00211             length ++;
00212             last = 1;
00213             continue;
00214           }
00215         }
00216       }
00217     } else if (current == 'C' && strncmp(text, "*/", 2) == 0) {
00218       // Close a C comment...
00219       *style++ = current;
00220       *style++ = current;
00221       text ++;
00222       length --;
00223       current = 'A';
00224       col += 2;
00225       continue;
00226     } else if (current == 'D') {
00227       // Continuing in string...
00228       if (strncmp(text, "\\\"", 2) == 0) {
00229         // Quoted end quote...
00230         *style++ = current;
00231         *style++ = current;
00232         text ++;
00233         length --;
00234         col += 2;
00235         continue;
00236       } else if (*text == '\"') {
00237         // End quote...
00238         *style++ = current;
00239         col ++;
00240         current = 'A';
00241         continue;
00242       }
00243     }
00244 
00245     // Copy style info...
00246     if (current == 'A' && (*text == '{' || *text == '}')) *style++ = 'G';
00247     else *style++ = current;
00248     col ++;
00249 
00250     last = isalnum(*text) || *text == '_' || *text == '.';
00251 
00252     if (*text == '\n') {
00253       // Reset column and possibly reset the style
00254       col = 0;
00255       if (current == 'B' || current == 'E') current = 'A';
00256     }
00257   }
00258 }
00259 
00260 // 'style_unfinished_cb()' - Update unfinished styles.
00261 void CodeEditor::style_unfinished_cb(int, void*) { }
00262 
00263 // 'style_update()' - Update the style buffer...
00264 void CodeEditor::style_update(int pos, int nInserted, int nDeleted,
00265                               int /*nRestyled*/, const char * /*deletedText*/,
00266                               void *cbArg) {
00267   CodeEditor    *editor = (CodeEditor *)cbArg;
00268   int           start,                          // Start of text
00269                 end;                            // End of text
00270   char          last,                           // Last style on line
00271                 *style,                         // Style data
00272                 *text;                          // Text data
00273 
00274 
00275   // If this is just a selection change, just unselect the style buffer...
00276   if (nInserted == 0 && nDeleted == 0) {
00277     editor->mStyleBuffer->unselect();
00278     return;
00279   }
00280 
00281   // Track changes in the text buffer...
00282   if (nInserted > 0) {
00283     // Insert characters into the style buffer...
00284     style = new char[nInserted + 1];
00285     memset(style, 'A', nInserted);
00286     style[nInserted] = '\0';
00287 
00288     editor->mStyleBuffer->replace(pos, pos + nDeleted, style);
00289     delete[] style;
00290   } else {
00291     // Just delete characters in the style buffer...
00292     editor->mStyleBuffer->remove(pos, pos + nDeleted);
00293   }
00294 
00295   // Select the area that was just updated to avoid unnecessary
00296   // callbacks...
00297   editor->mStyleBuffer->select(pos, pos + nInserted - nDeleted);
00298 
00299   // Re-parse the changed region; we do this by parsing from the
00300   // beginning of the line of the changed region to the end of
00301   // the line of the changed region...  Then we check the last
00302   // style character and keep updating if we have a multi-line
00303   // comment character...
00304   start = editor->mBuffer->line_start(pos);
00305   end   = editor->mBuffer->line_end(pos + nInserted);
00306   text  = editor->mBuffer->text_range(start, end);
00307   style = editor->mStyleBuffer->text_range(start, end);
00308   if (start==end)
00309     last = 0;
00310   else
00311     last  = style[end - start - 1];
00312 
00313   style_parse(text, style, end - start);
00314 
00315   editor->mStyleBuffer->replace(start, end, style);
00316   editor->redisplay_range(start, end);
00317 
00318   if (start==end || last != style[end - start - 1]) {
00319     // The last character on the line changed styles, so reparse the
00320     // remainder of the buffer...
00321     free(text);
00322     free(style);
00323 
00324     end   = editor->mBuffer->length();
00325     text  = editor->mBuffer->text_range(start, end);
00326     style = editor->mStyleBuffer->text_range(start, end);
00327 
00328     style_parse(text, style, end - start);
00329 
00330     editor->mStyleBuffer->replace(start, end, style);
00331     editor->redisplay_range(start, end);
00332   }
00333 
00334   free(text);
00335   free(style);
00336 }
00337 
00338 int CodeEditor::auto_indent(int, CodeEditor* e) {
00339   if (e->buffer()->selected()) {
00340     e->insert_position(e->buffer()->primary_selection()->start());
00341     e->buffer()->remove_selection();
00342   }
00343 
00344   int pos = e->insert_position();
00345   int start = e->line_start(pos);
00346   char *text = e->buffer()->text_range(start, pos);
00347   char *ptr;
00348 
00349   for (ptr = text; isspace(*ptr); ptr ++);
00350   *ptr = '\0';  
00351   if (*text) {
00352     // use only a single 'insert' call to avoid redraw issues
00353     int n = strlen(text);
00354     char *b = (char*)malloc(n+2);
00355     *b = '\n';
00356     strcpy(b+1, text);
00357     e->insert(b);
00358     free(b);
00359   } else {
00360     e->insert("\n");
00361   }
00362   e->show_insert_position();
00363   e->set_changed();
00364   if (e->when()&FL_WHEN_CHANGED) e->do_callback();
00365 
00366   free(text);
00367 
00368   return 1;
00369 }
00370 
00371 // Create a CodeEditor widget...
00372 CodeEditor::CodeEditor(int X, int Y, int W, int H, const char *L) :
00373   Fl_Text_Editor(X, Y, W, H, L) {
00374   buffer(new Fl_Text_Buffer);
00375 
00376   char *style = new char[mBuffer->length() + 1];
00377   char *text = mBuffer->text();
00378 
00379   memset(style, 'A', mBuffer->length());
00380   style[mBuffer->length()] = '\0';
00381 
00382   highlight_data(new Fl_Text_Buffer(mBuffer->length()), styletable,
00383                  sizeof(styletable) / sizeof(styletable[0]),
00384                  'A', style_unfinished_cb, this);
00385 
00386   style_parse(text, style, mBuffer->length());
00387 
00388   mStyleBuffer->text(style);
00389   delete[] style;
00390   free(text);
00391 
00392   mBuffer->add_modify_callback(style_update, this);
00393   add_key_binding(FL_Enter, FL_TEXT_EDITOR_ANY_STATE,
00394                   (Fl_Text_Editor::Key_Func)auto_indent);
00395 }
00396 
00397 // Destroy a CodeEditor widget...
00398 CodeEditor::~CodeEditor() {
00399   Fl_Text_Buffer *buf = mStyleBuffer;
00400   mStyleBuffer = 0;
00401   delete buf;
00402 
00403   buf = mBuffer;
00404   buffer(0);
00405   delete buf;
00406 }
00407 
00408 
00409 CodeViewer::CodeViewer(int X, int Y, int W, int H, const char *L)
00410 : CodeEditor(X, Y, W, H, L) 
00411 {
00412   default_key_function(kf_ignore);  
00413   remove_all_key_bindings(&key_bindings);
00414   cursor_style(CARET_CURSOR);
00415 }
00416 
00417 //
00418 // End of "$Id: CodeEditor.cxx 7903 2010-11-28 21:06:39Z matt $".
00419 //