// // "$Id: editor.cxx 8864 2011-07-19 04:49:30Z greg.ercolano $" // // A simple text editor program for the Fast Light Tool Kit (FLTK). // // This program is described in Chapter 4 of the FLTK Programmer's Guide. // // Copyright 1998-2010 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this // file is missing or damaged, see the license at: // // http://www.fltk.org/COPYING.php // // Please report all bugs and problems on the following page: // // http://www.fltk.org/str.php // // // Include necessary headers... // /* #include #include #include #include #include #ifdef __MWERKS__ # define FL_DLL #endif #include #include #include #include #include #include #include #include #include #include #include #include */ local WIN32 = os.getenv("WINDIR") != null local __APPLE__ = os.getenv("MAC??") != null local changed = 0; local filename = ""; local title=""; local textbuf = null; // Syntax highlighting stuff... const TS = 14; // default editor textsize local stylebuf = null; local styletable = [ // Style table [ FL_BLACK, FL_COURIER, TS ], // A - Plain [ FL_DARK_GREEN, FL_HELVETICA_ITALIC, TS ], // B - Line comments [ FL_DARK_GREEN, FL_HELVETICA_ITALIC, TS ], // C - Block comments [ FL_BLUE, FL_COURIER, TS ], // D - Strings [ FL_DARK_RED, FL_COURIER, TS ], // E - Directives [ FL_DARK_RED, FL_COURIER_BOLD, TS ], // F - Types [ FL_BLUE, FL_COURIER_BOLD, TS ], // G - Keywords ]; local code_keywords = [ // List of known C/C++ keywords... "and", "and_eq", "asm", "bitand", "bitor", "break", "case", "catch", "compl", "continue", "default", "delete", "do", "else", "false", "for", "goto", "if", "new", "not", "not_eq", "operator", "or", "or_eq", "return", "switch", "template", "this", "throw", "true", "try", "while", "xor", "xor_eq" ]; local code_types = [ // List of known C/C++ types... "auto", "bool", "char", "class", "const", "const_cast", "double", "dynamic_cast", "enum", "explicit", "extern", "float", "friend", "inline", "int", "long", "mutable", "namespace", "private", "protected", "public", "register", "short", "signed", "sizeof", "static", "static_cast", "struct", "template", "typedef", "typename", "union", "unsigned", "virtual", "void", "volatile" ]; // // 'compare_keywords()' - Compare two keywords... // //extern "C" { // int function compare_keywords(a, b) { return a <=> b; } //} // // 'style_parse()' - Parse text and produce style data. // //void function style_parse(text, style, length) { local current; local col; local last; local buf, bufptr; local temp; local istyle = 0; local itext = 0; // Style letters: // // A - Plain // B - Line comments // C - Block comments // D - Strings // E - Directives // F - Types // G - Keywords //~ for (current = style[istyle], col = 0, last = 0; length > 0; length --, itext ++) { //~ if (current == 'B' || current == 'F' || current == 'G') current = 'A'; //~ if (current == 'A') { //~ // Check for directives, comments, strings, and keywords... //~ if (col == 0 && text[itext] == '#') { //~ // Set style to directive //~ current = 'E'; //~ } else if (strncmp(text, "//", 2) == 0) { //~ current = 'B'; //~ for (; length > 0 && text[itext] != '\n'; length --, itext ++) istyle++ = 'B'; //~ if (length == 0) break; //~ } else if (strncmp(text, "/*", 2) == 0) { //~ current = 'C'; //~ } else if (strncmp(text, "\\\"", 2) == 0) { //~ // Quoted quote... //~ style[istyle++] = current; //~ style[style++] = current; //~ itext ++; //~ length --; //~ col += 2; //~ continue; //~ } else if (text[itext] == '\"') { //~ current = 'D'; //~ } else if (!last && (islower((text[itext])&255) || text[itext] == '_')) { //~ // Might be a keyword... //~ for (temp = text[itext], bufptr = buf; //~ (islower((temp)&255) || temp == '_') && bufptr < (buf + sizeof(buf) - 1); //~ *bufptr++ = *temp++); //~ if (!islower((*temp)&255) && *temp != '_') { //~ *bufptr = '\0'; //~ bufptr = buf; //~ if (bsearch(&bufptr, code_types, //~ sizeof(code_types) / sizeof(code_types[0]), //~ sizeof(code_types[0]), compare_keywords)) { //~ while (text < temp) { //~ style[istyle++] = 'F'; //~ itext ++; //~ length --; //~ col ++; //~ } //~ itext --; //~ length ++; //~ last = 1; //~ continue; //~ } else if (bsearch(&bufptr, code_keywords, //~ sizeof(code_keywords) / sizeof(code_keywords[0]), //~ sizeof(code_keywords[0]), compare_keywords)) { //~ while (text < temp) { //~ style[istyle++] = 'G'; //~ itext ++; //~ length --; //~ col ++; //~ } //~ itext --; //~ length ++; //~ last = 1; //~ continue; //~ } //~ } //~ } //~ } else if (current == 'C' && strncmp(text, "*/", 2) == 0) { //~ // Close a C comment... //~ style[istyle++] = current; //~ istyle++ = current; //~ itext ++; //~ length --; //~ current = 'A'; //~ col += 2; //~ continue; //~ } else if (current == 'D') { //~ // Continuing in string... //~ if (strncmp(text[itext], "\\\"", 2) == 0) { //~ // Quoted end quote... //~ style[istyle++] = current; //~ style[istyle++] = current; //~ itext ++; //~ length --; //~ col += 2; //~ continue; //~ } else if (text[itext] == '\"') { //~ // End quote... //~ style[istyle++] = current; //~ col ++; //~ current = 'A'; //~ continue; //~ } //~ } //~ // Copy style info... //~ if (current == 'A' && (text[itext] == '{' || text[itext] == '}')) style[style++] = 'G'; //~ else style[istyle++] = current; //~ col ++; //~ last = isalnum((text[itext])&255) || text[itext] == '_' || text[itext] == '.'; //~ if (text[itext] == '\n') { //~ // Reset column and possibly reset the style //~ col = 0; //~ if (current == 'B' || current == 'E') current = 'A'; //~ } //~ } } // // 'style_init()' - Initialize the style buffer... // //void function style_init() { local style = new blob(textbuf->length()+1); local text = textbuf->text(); style.memset(0, 'A', textbuf->length()); style[textbuf->length()] = '\0'; if (!stylebuf) stylebuf = new Fl_Text_Buffer(textbuf->length()); style_parse(text, style, textbuf->length()); stylebuf->text(style.tostring()); //delete[] style; //free(text); } // // 'style_unfinished_cb()' - Update unfinished styles. // //void function style_unfinished_cb(pint, pvoid) { } // // 'style_update()' - Update the style buffer... // //void function style_update(pos, // I - Position of update nInserted, // I - Number of inserted chars nDeleted, // I - Number of deleted chars nRestyled, // I - Number of restyled chars deletedText,// I - Text that was deleted cbArg) { // I - Callback data local start, // Start of text end; // End of text local last, // Last style on line style, // Style data text; // Text data // If this is just a selection change, just unselect the style buffer... if (nInserted == 0 && nDeleted == 0) { stylebuf->unselect(); return; } // Track changes in the text buffer... if (nInserted > 0) { // Insert characters into the style buffer... style = new char[nInserted + 1]; memset(style, 'A', nInserted); style[nInserted] = '\0'; stylebuf->replace(pos, pos + nDeleted, style); //delete[] style; } else { // Just delete characters in the style buffer... stylebuf->remove(pos, pos + nDeleted); } // Select the area that was just updated to avoid unnecessary // callbacks... stylebuf->select(pos, pos + nInserted - nDeleted); // Re-parse the changed region; we do this by parsing from the // beginning of the previous line of the changed region to the end of // the line of the changed region... Then we check the last // style character and keep updating if we have a multi-line // comment character... start = textbuf->line_start(pos); // if (start > 0) start = textbuf->line_start(start - 1); end = textbuf->line_end(pos + nInserted); text = textbuf->text_range(start, end); style = stylebuf->text_range(start, end); if (start==end) last = 0; else last = style[end - start - 1]; // printf("start = %d, end = %d, text = \"%s\", style = \"%s\", last='%c'...\n", // start, end, text, style, last); style_parse(text, style, end - start); // printf("new style = \"%s\", new last='%c'...\n", // style, style[end - start - 1]); stylebuf->replace(start, end, style); /*((Fl_Text_Editor *)*/cbArg->redisplay_range(start, end); if (start==end || last != style[end - start - 1]) { // printf("Recalculate the rest of the buffer style\n"); // Either the user deleted some text, or the last character // on the line changed styles, so reparse the // remainder of the buffer... //free(text); //free(style); end = textbuf->length(); text = textbuf->text_range(start, end); style = stylebuf->text_range(start, end); style_parse(text, style, end - start); stylebuf->replace(start, end, style); /*((Fl_Text_Editor *)*/cbArg->redisplay_range(start, end); } //free(text); //free(style); } // Editor window functions and class... /* void save_cb(); void saveas_cb(); void find2_cb(Fl_Widget*, void*); void replall_cb(Fl_Widget*, void*); void replace2_cb(Fl_Widget*, void*); void replcan_cb(Fl_Widget*, void*); */ class EditorWindow extends Fl_Double_Window { //public: replace_dlg = null; replace_find = null; replace_with = null; replace_all = null; replace_next = null; replace_cancel = null; editor = null; search = null; constructor(iw, ih, t) { base.constructor(iw, ih, t); replace_dlg = new Fl_Window(300, 105, "Replace"); replace_find = new Fl_Input(80, 10, 210, 25, "Find:"); replace_find->align(FL_ALIGN_LEFT); replace_with = new Fl_Input(80, 40, 210, 25, "Replace:"); replace_with->align(FL_ALIGN_LEFT); replace_all = new Fl_Button(10, 70, 90, 25, "Replace All"); replace_all->callback(replall_cb, this); replace_next = new Fl_Return_Button(105, 70, 120, 25, "Replace Next"); replace_next->callback(replace2_cb, this); replace_cancel = new Fl_Button(230, 70, 60, 25, "Cancel"); replace_cancel->callback(replcan_cb, this); replace_dlg->end(); replace_dlg->set_non_modal(); editor = 0; search = ""; } //destructor() { // delete replace_dlg; //} }; function check_save() { if (!changed) return 1; local r = fl_choice("The current file has not been saved.\nWould you like to save it now?", "Cancel", "Save", "Don't Save"); if (r == 1) { save_cb(); // Save the file... return !changed; } return (r == 2) ? 1 : 0; } local loading = 0; function load_file(newfile, ipos) { loading = 1; local insert = (ipos != -1); changed = insert; if (!insert) filename = ""; local r; if (!insert) r = textbuf->loadfile(newfile); else r = textbuf->insertfile(newfile, ipos); changed = changed || textbuf->input_file_was_transcoded; if (r) fl_alert("Error reading from file \'%s\':\n%s.", newfile, strerror(errno)); else if (!insert) filename = newfile; loading = 0; //textbuf->call_modify_callbacks(); } function save_file(newfile) { if (textbuf->savefile(newfile)) fl_alert("Error writing to file \'%s\':\n%s.", newfile, strerror(errno)); else filename = newfile; changed = 0; textbuf->call_modify_callbacks(); } function copy_cb(sender, e) { Fl_Text_Editor.kf_copy(0, e->editor); } function cut_cb(sender, e) { Fl_Text_Editor.kf_cut(0, e->editor); } function delete_cb(sender, v) { textbuf->remove_selection(); } function find_cb(w, e) { local val; val = fl_input("Search String:", e->search); if (val != null) { // User entered a string - go find it! e->search = val; find2_cb(w, v); } } function find2_cb(w, e) { if (e->search.len() == 0) { // Search string is blank; get a new one... find_cb(w, v); return; } local pos = e->editor->insert_position(); local found = textbuf->search_forward(pos, e->search/*, &pos*/); if (found) { // Found a match; select and update the position... textbuf->select(pos, pos+strlen(e->search)); e->editor->insert_position(pos+e->search.len()); e->editor->show_insert_position(); } else fl_alert("No occurrences of \'%s\' found!", e->search); } function set_title(w) { if (filename.len() == 0) title = "Untitled"; else { local slash = filename.rfind("/"); if( WIN32 && slash == null) slash = filename.find("\\"); if (slash != null) title = filename.slice(slash + 1); else title = filename; } if (changed) title += " (modified)"; w->label(title); } function changed_cb(iparam, nInserted, nDeleted, iparam2, strparam, w) { if ((nInserted || nDeleted) && !loading) changed = 1; set_title(w); if (loading) w->editor->show_insert_position(); } function new_cb(sender, udata) { if (!check_save()) return; filename = ""; textbuf->select(0, textbuf->length()); textbuf->remove_selection(); changed = 0; textbuf->call_modify_callbacks(); } function open_cb(sender, udata) { if (!check_save()) return; local fnfc = Fl_Native_File_Chooser(); fnfc.title("Open file"); fnfc.type(Fl_Native_File_Chooser.BROWSE_FILE); if ( fnfc.show() ) return; load_file(fnfc.filename(), -1); } function insert_cb(sender, w) { local fnfc = Fl_Native_File_Chooser(); fnfc.title("Insert file"); fnfc.type(Fl_Native_File_Chooser.BROWSE_FILE); if ( fnfc.show() ) return; load_file(fnfc.filename(), w->editor->insert_position()); } function paste_cb(sender, e) { Fl_Text_Editor.kf_paste(0, e->editor); } local num_windows = 0; function close_cb(sender, w) { if (num_windows == 1) { if (!check_save()) return; } w->hide(); w->editor->buffer(0); textbuf->remove_modify_callback(style_update, w->editor); textbuf->remove_modify_callback(changed_cb, w); Fl.delete_widget(w); num_windows--; if (!num_windows) os.exit(0); } function quit_cb(sender, udata) { if (changed && !check_save()) return; os.exit(0); } function replace_cb(sender, e) { e->replace_dlg->show(); } function replace2_cb(sender, e) { local find = e->replace_find->value(); local replace = e->replace_with->value(); if (find.len() == 0) { // Search string is blank; get a new one... e->replace_dlg->show(); return; } e->replace_dlg->hide(); local pos = e->editor->insert_position(); local found = textbuf->search_forward(pos, find /*, &pos*/); if (found) { // Found a match; update the position and replace text... textbuf->select(pos, pos+strlen(find)); textbuf->remove_selection(); textbuf->insert(pos, replace); textbuf->select(pos, pos+replace.len()); e->editor->insert_position(pos+replace.len()); e->editor->show_insert_position(); } else fl_alert("No occurrences of \'%s\' found!", find); } function replall_cb(sender, e) { local find = e->replace_find->value(); local replace = e->replace_with->value(); find = e->replace_find->value(); if (find.len() == 0) { // Search string is blank; get a new one... e->replace_dlg->show(); return; } e->replace_dlg->hide(); e->editor->insert_position(0); local times = 0; // Loop through the whole string for (local found = 1; found;) { local pos = e->editor->insert_position(); found = textbuf->search_forward(pos, find/*, &pos*/); if (found) { // Found a match; update the position and replace text... textbuf->select(pos, pos+strlen(find)); textbuf->remove_selection(); textbuf->insert(pos, replace); e->editor->insert_position(pos+strlen(replace)); e->editor->show_insert_position(); times++; } } if (times) fl_message("Replaced %d occurrences.", times); else fl_alert("No occurrences of \'%s\' found!", find); } function replcan_cb(sender, e) { e->replace_dlg->hide(); } function save_cb() { if (filename.len() == 0) { // No filename - get one! saveas_cb(); return; } else save_file(filename); } function saveas_cb() { local fnfc = Fl_Native_File_Chooser(); fnfc.title("Save File As?"); fnfc.type(Fl_Native_File_Chooser.BROWSE_SAVE_FILE); if ( fnfc.show() ) return; save_file(fnfc.filename()); } function view_cb(sender, udata) { local w = new_view(); w->show(); } local menuitems = [ [ "&File", 0, 0, 0, FL_SUBMENU ], [ "&New File", 0, new_cb ], [ "&Open File...", FL_COMMAND + 'o', open_cb ], [ "&Insert File...", FL_COMMAND + 'i', insert_cb, 0, FL_MENU_DIVIDER ], [ "&Save File", FL_COMMAND + 's', save_cb ], [ "Save File &As...", FL_COMMAND + FL_SHIFT + 's', saveas_cb, 0, FL_MENU_DIVIDER ], [ "New &View", FL_ALT + (__APPLE__ ? FL_COMMAND : 0) + 'v', view_cb, 0 ], [ "&Close View", FL_COMMAND + 'w', close_cb, 0, FL_MENU_DIVIDER ], [ "E&xit", FL_COMMAND + 'q', quit_cb, 0 ], [ 0 ], [ "&Edit", 0, 0, 0, FL_SUBMENU ], [ "Cu&t", FL_COMMAND + 'x', cut_cb ], [ "&Copy", FL_COMMAND + 'c', copy_cb ], [ "&Paste", FL_COMMAND + 'v', paste_cb ], [ "&Delete", 0, delete_cb ], [ 0 ], [ "&Search", 0, 0, 0, FL_SUBMENU ], [ "&Find...", FL_COMMAND + 'f', find_cb ], [ "F&ind Again", FL_COMMAND + 'g', find2_cb ], [ "&Replace...", FL_COMMAND + 'r', replace_cb ], [ "Re&place Again", FL_COMMAND + 't', replace2_cb ], [ 0 ], [ 0 ] ]; function mergerSubMenu(prefix, amenu, curr_pos, last_pos){ for(; curr_pos < last_pos; ++curr_pos){ local item = amenu[curr_pos]; if(!item[0]) break; if(prefix){ item[0] = prefix + "/" + item[0]; } if(item.len() > 4 && item[4] & FL_SUBMENU){ curr_pos = mergerSubMenu(item[0], amenu, ++curr_pos, last_pos); } } return curr_pos; } mergerSubMenu(null, menuitems, 0, menuitems.len()); function new_view() { local w = new EditorWindow(660, 400, title); w->begin(); local m = new Fl_Menu_Bar(0, 0, 660, 30); m->copy(menuitems, w); w->editor = new Fl_Text_Editor(0, 30, 660, 370); w->editor->textfont(FL_COURIER); w->editor->textsize(TS); //w->editor->wrap_mode(Fl_Text_Editor::WRAP_AT_BOUNDS, 250); w->editor->buffer(textbuf); w->editor->highlight_data(stylebuf, styletable, styletable.len(), 'A', style_unfinished_cb, 0); textbuf->text(); style_init(); w->end(); w->resizable(w->editor); //w->callback(close_cb, w); //textbuf->add_modify_callback(style_update, w->editor); //textbuf->add_modify_callback(changed_cb, w); //textbuf->call_modify_callbacks(); num_windows++; return w; } function main(argc, argv) { textbuf = new Fl_Text_Buffer(); //textbuf->transcoding_warning_action = NULL; style_init(); local window = new_view(); //window->show(1, argv); window->show(); if (argc > 0) load_file(argv[0], -1); load_file("editor-fltk.nut", -1); return Fl.run(); } main(vargv.len(), vargv); // // End of "$Id: editor.cxx 8864 2011-07-19 04:49:30Z greg.ercolano $". //