Index: Fl_Type.h
===================================================================
--- Fl_Type.h	(revision 11800)
+++ Fl_Type.h	(working copy)
@@ -31,6 +31,7 @@
 #include "Fluid_Image.h"
 #include <FL/fl_draw.H>
 #include <stdarg.h>
+#include "ExternalCodeEditor.h"
 
 void set_modflag(int mf);
 
@@ -109,7 +110,7 @@
   virtual void open();	// what happens when you double-click
 
   // read and write data to a saved file:
-  void write();
+  virtual void write();
   virtual void write_properties();
   virtual void read_property(const char *);
   virtual int read_fdesign(const char*, const char*);
@@ -143,6 +144,7 @@
   virtual int is_menu_button() const;
   virtual int is_group() const;
   virtual int is_window() const;
+  virtual int is_code() const;
   virtual int is_code_block() const;
   virtual int is_decl_block() const;
   virtual int is_comment() const;
@@ -185,15 +187,67 @@
 };
 
 class Fl_Code_Type : public Fl_Type {
+  ExternalCodeEditor editor_;
 public:
   Fl_Type *make();
+  void write();
   void write_code1();
   void write_code2();
   void open();
   virtual const char *type_name() {return "code";}
   int is_code_block() const {return 0;}
+  int is_code() const {return 1;}
   int pixmapID() { return 8; }
   virtual int is_public() const;
+  // See if external editor is open
+  int is_editing() {
+    return editor_.is_editing();
+  }
+  // Reap the editor's pid
+  // Returns:
+  //   -2 -- editor not open
+  //   -1 -- waitpid() failed (errno has reason)
+  //    0 -- process still running
+  //   >0 -- process finished + reaped (value is pid)
+  int reap_editor() {
+    return editor_.reap_editor();
+  }
+  // See if editor file changed
+  //    Returns 1 if so, 0 if not, -1 on error.
+  //    If changed, record keeping is updated and file's contents is loaded into ram
+  //
+  int changed_update() {
+    int changed = 0;
+    if ( editor_.is_editing() ) {
+      switch ( editor_.changed_update() ) {
+        case 0: { // unchanged
+          break;
+        }
+        case 1: { // changed
+          ++changed;
+          // TODO: Figure out how saving a fluid file
+          //       can be intercepted to grab current contents
+          //       of editor file..
+          //
+          const char *s = 0;
+          switch ( editor_.get_code(&s) ) {     // get code
+            case 0:             // Success
+              name(s);          // update value in ram
+              free((void*)s);   // free string get_code() returns
+              break;
+            case -1:            // Failed: couldn't read file (dialog showed reason)
+              return(-1);       // return, something is very wrong
+            case -2:            // Failed: editor not open (dialog showed reason)
+              break;            // fall thru, use value in ram
+          }
+        }
+        case -1: {    // error
+          break;      // shrug - probably dont want to post a dialog
+        }
+      }
+    }
+    return changed;
+  }
 };
 
 class Fl_CodeBlock_Type : public Fl_Type {
Index: alignment_panel.fl
===================================================================
--- alignment_panel.fl	(revision 11800)
+++ alignment_panel.fl	(working copy)
@@ -46,6 +46,12 @@
 decl {extern int show_comments;} {public local
 }
 
+decl {extern int G_use_external_editor;} {public local
+}
+
+decl {extern char G_external_editor_command[512];} {public local
+}
+
 decl {extern int show_coredevmenus;} {public local
 }
 
@@ -58,8 +64,7 @@
 decl {Fl_Text_Buffer *shell_run_buffer;} {public local
 }
 
-Function {make_project_window()} {open
-} {
+Function {make_project_window()} {} {
   Fl_Window project_window {
     label {Project Settings} open
     xywh {396 475 399 252} type Double hide
@@ -129,7 +134,7 @@
         }
         Fl_Input i18n_include_input {
           label {\#include:}
-          callback i18n_text_cb selected
+          callback i18n_text_cb
           tooltip {The include file for internationalization.} xywh {100 78 272 20} box THIN_DOWN_BOX labelfont 1 textfont 4
         }
         Fl_Input i18n_file_input {
@@ -158,10 +163,11 @@
 decl {void scheme_cb(Fl_Choice *, void *);} {public local
 }
 
-Function {make_settings_window()} {} {
+Function {make_settings_window()} {open
+} {
   Fl_Window settings_window {
     label {GUI Settings} open
-    xywh {393 191 349 241} type Double hide non_modal
+    xywh {355 85 360 355} type Double non_modal visible
   } {
     Fl_Choice scheme_choice {
       label {Scheme: }
@@ -194,17 +200,17 @@
       }
     }
     Fl_Group {} {open
-      xywh {116 43 220 126} labelfont 1 align 0
+      xywh {20 43 330 161} labelfont 1 align 0
     } {
       Fl_Box {} {
         label {Options: }
-        xywh {140 43 1 25} labelfont 1 align 4
+        xywh {139 43 1 25} labelfont 1 align 4
       }
       Fl_Check_Button tooltips_button {
         label {Show Tooltips}
         callback {Fl_Tooltip::enable(tooltips_button->value());
 fluid_prefs.set("show_tooltips", tooltips_button->value());}
-        xywh {138 43 113 25} down_box DOWN_BOX labelsize 12
+        xywh {137 43 113 25} down_box DOWN_BOX labelsize 12
         code0 {int b;}
         code1 {fluid_prefs.get("show_tooltips", b, 1);}
         code2 {tooltips_button->value(b);}
@@ -213,7 +219,7 @@
       Fl_Check_Button completion_button {
         label {Show Completion Dialogs}
         callback {fluid_prefs.set("show_completion_dialogs", completion_button->value());}
-        xywh {138 68 186 25} down_box DOWN_BOX labelsize 12
+        xywh {137 68 186 25} down_box DOWN_BOX labelsize 12
         code0 {int b;}
         code1 {fluid_prefs.get("show_completion_dialogs", b, 1);}
         code2 {completion_button->value(b);}
@@ -221,7 +227,7 @@
       Fl_Check_Button openlast_button {
         label {Open Previous File on Startup}
         callback {fluid_prefs.set("open_previous_file", openlast_button->value());}
-        xywh {138 93 214 25} down_box DOWN_BOX labelsize 12
+        xywh {137 93 213 25} down_box DOWN_BOX labelsize 12
         code0 {int b;}
         code1 {fluid_prefs.get("open_previous_file", b, 0);}
         code2 {openlast_button->value(b);}
@@ -229,7 +235,7 @@
       Fl_Check_Button prevpos_button {
         label {Remember Window Positions}
         callback {fluid_prefs.set("prev_window_pos", prevpos_button->value());}
-        xywh {138 118 209 25} down_box DOWN_BOX labelsize 12
+        xywh {137 117 209 25} down_box DOWN_BOX labelsize 12
         code0 {int b;}
         code1 {fluid_prefs.get("prev_window_pos", b, 1);}
         code2 {prevpos_button->value(b);}
@@ -239,7 +245,7 @@
         callback {show_comments = show_comments_button->value();
 fluid_prefs.set("show_comments", show_comments);
 redraw_browser();}
-        xywh {138 143 209 25} down_box DOWN_BOX labelsize 12
+        xywh {137 141 209 25} down_box DOWN_BOX labelsize 12
         code1 {fluid_prefs.get("show_comments", show_comments, 1);}
         code2 {show_comments_button->value(show_comments);}
       }
@@ -248,16 +254,44 @@
       label {\# Recent Files: }
       callback {fluid_prefs.set("recent_files", recent_spinner->value());
 load_history();}
-      xywh {140 173 40 25} labelfont 1 when 1
+      xywh {138 170 40 25} labelfont 1 when 1
       code0 {int c;}
       code1 {fluid_prefs.get("recent_files", c, 5);}
       code2 {recent_spinner->maximum(10);}
       code3 {recent_spinner->value(c);}
     }
+    Fl_Group {} {open
+      xywh {10 210 337 95} box THIN_UP_BOX color 47
+    } {
+      Fl_Check_Button use_external_editor_button {
+        label {Use external editor?}
+        callback {G_use_external_editor = use_external_editor_button->value();
+fluid_prefs.set("use_external_editor", G_use_external_editor);
+redraw_browser();}
+        xywh {25 218 209 22} down_box DOWN_BOX labelsize 12
+        code1 {fluid_prefs.get("use_external_editor", G_use_external_editor, 0);}
+        code2 {use_external_editor_button->value(G_use_external_editor);}
+      }
+      Fl_Input editor_command_input {
+        label {Editor Command}
+        callback {strncpy(G_external_editor_command, editor_command_input->value(), sizeof(G_external_editor_command)-1);
+G_external_editor_command[sizeof(G_external_editor_command)-1] = 0;
+fluid_prefs.set("external_editor_command", G_external_editor_command);
+redraw_browser();}
+        tooltip {The editor command to open your external text editor.
+Include any necessary flags to ensure your editor does not background itself.
+Examples:
+    gvim -f
+    gedit
+ emacs} xywh {25 264 305 21} labelsize 10 align 5 when 1 textsize 10
+        code1 {fluid_prefs.get("external_editor_command", G_external_editor_command, "", sizeof(G_external_editor_command)-1);}
+        code2 {editor_command_input->value(G_external_editor_command);}
+      }
+    }
     Fl_Button {} {
       label Close
-      callback {settings_window->hide();}
-      tooltip {Close this dialog.} xywh {276 205 64 25}
+      callback {settings_window->hide();} selected
+      tooltip {Close this dialog.} xywh {285 320 64 25}
     }
   }
 }
Index: alignment_panel.cxx
===================================================================
--- alignment_panel.cxx	(revision 11800)
+++ alignment_panel.cxx	(working copy)
@@ -197,12 +197,29 @@
 load_history();
 }
 
+Fl_Check_Button *use_external_editor_button=(Fl_Check_Button *)0;
+
+static void cb_use_external_editor_button(Fl_Check_Button*, void*) {
+  G_use_external_editor = use_external_editor_button->value();
+fluid_prefs.set("use_external_editor", G_use_external_editor);
+redraw_browser();
+}
+
+Fl_Input *editor_command_input=(Fl_Input *)0;
+
+static void cb_editor_command_input(Fl_Input*, void*) {
+  strncpy(G_external_editor_command, editor_command_input->value(), sizeof(G_external_editor_command)-1);
+G_external_editor_command[sizeof(G_external_editor_command)-1] = 0;
+fluid_prefs.set("external_editor_command", G_external_editor_command);
+redraw_browser();
+}
+
 static void cb_Close1(Fl_Button*, void*) {
   settings_window->hide();
 }
 
 Fl_Double_Window* make_settings_window() {
-  { settings_window = new Fl_Double_Window(349, 241, "GUI Settings");
+  { settings_window = new Fl_Double_Window(360, 355, "GUI Settings");
     { scheme_choice = new Fl_Choice(140, 10, 115, 25, "Scheme: ");
       scheme_choice->down_box(FL_BORDER_BOX);
       scheme_choice->labelfont(1);
@@ -213,14 +230,14 @@
       scheme_choice->value(s);
       scheme_cb(0, 0);
     } // Fl_Choice* scheme_choice
-    { Fl_Group* o = new Fl_Group(116, 43, 220, 126);
+    { Fl_Group* o = new Fl_Group(20, 43, 330, 161);
       o->labelfont(1);
       o->align(Fl_Align(FL_ALIGN_CENTER));
-      { Fl_Box* o = new Fl_Box(140, 43, 1, 25, "Options: ");
+      { Fl_Box* o = new Fl_Box(139, 43, 1, 25, "Options: ");
         o->labelfont(1);
         o->align(Fl_Align(FL_ALIGN_LEFT));
       } // Fl_Box* o
-      { tooltips_button = new Fl_Check_Button(138, 43, 113, 25, "Show Tooltips");
+      { tooltips_button = new Fl_Check_Button(137, 43, 113, 25, "Show Tooltips");
         tooltips_button->down_box(FL_DOWN_BOX);
         tooltips_button->labelsize(12);
         tooltips_button->callback((Fl_Callback*)cb_tooltips_button);
@@ -229,7 +246,7 @@
         tooltips_button->value(b);
         Fl_Tooltip::enable(b);
       } // Fl_Check_Button* tooltips_button
-      { completion_button = new Fl_Check_Button(138, 68, 186, 25, "Show Completion Dialogs");
+      { completion_button = new Fl_Check_Button(137, 68, 186, 25, "Show Completion Dialogs");
         completion_button->down_box(FL_DOWN_BOX);
         completion_button->labelsize(12);
         completion_button->callback((Fl_Callback*)cb_completion_button);
@@ -237,7 +254,7 @@
         fluid_prefs.get("show_completion_dialogs", b, 1);
         completion_button->value(b);
       } // Fl_Check_Button* completion_button
-      { openlast_button = new Fl_Check_Button(138, 93, 214, 25, "Open Previous File on Startup");
+      { openlast_button = new Fl_Check_Button(137, 93, 213, 25, "Open Previous File on Startup");
         openlast_button->down_box(FL_DOWN_BOX);
         openlast_button->labelsize(12);
         openlast_button->callback((Fl_Callback*)cb_openlast_button);
@@ -245,7 +262,7 @@
         fluid_prefs.get("open_previous_file", b, 0);
         openlast_button->value(b);
       } // Fl_Check_Button* openlast_button
-      { prevpos_button = new Fl_Check_Button(138, 118, 209, 25, "Remember Window Positions");
+      { prevpos_button = new Fl_Check_Button(137, 117, 209, 25, "Remember Window Positions");
         prevpos_button->down_box(FL_DOWN_BOX);
         prevpos_button->labelsize(12);
         prevpos_button->callback((Fl_Callback*)cb_prevpos_button);
@@ -253,7 +270,7 @@
         fluid_prefs.get("prev_window_pos", b, 1);
         prevpos_button->value(b);
       } // Fl_Check_Button* prevpos_button
-      { show_comments_button = new Fl_Check_Button(138, 143, 209, 25, "Show Comments in Browser");
+      { show_comments_button = new Fl_Check_Button(137, 141, 209, 25, "Show Comments in Browser");
         show_comments_button->down_box(FL_DOWN_BOX);
         show_comments_button->labelsize(12);
         show_comments_button->callback((Fl_Callback*)cb_show_comments_button);
@@ -262,7 +279,7 @@
       } // Fl_Check_Button* show_comments_button
       o->end();
     } // Fl_Group* o
-    { recent_spinner = new Fl_Spinner(140, 173, 40, 25, "# Recent Files: ");
+    { recent_spinner = new Fl_Spinner(138, 170, 40, 25, "# Recent Files: ");
       recent_spinner->labelfont(1);
       recent_spinner->callback((Fl_Callback*)cb_recent_spinner);
       recent_spinner->when(FL_WHEN_CHANGED);
@@ -271,7 +288,31 @@
       recent_spinner->maximum(10);
       recent_spinner->value(c);
     } // Fl_Spinner* recent_spinner
-    { Fl_Button* o = new Fl_Button(276, 205, 64, 25, "Close");
+    { Fl_Group* o = new Fl_Group(10, 210, 337, 95);
+      o->box(FL_THIN_UP_BOX);
+      o->color(FL_DARK1);
+      { use_external_editor_button = new Fl_Check_Button(25, 218, 209, 22, "Use external editor?");
+        use_external_editor_button->down_box(FL_DOWN_BOX);
+        use_external_editor_button->labelsize(12);
+        use_external_editor_button->callback((Fl_Callback*)cb_use_external_editor_button);
+        fluid_prefs.get("use_external_editor", G_use_external_editor, 0);
+        use_external_editor_button->value(G_use_external_editor);
+      } // Fl_Check_Button* use_external_editor_button
+      { editor_command_input = new Fl_Input(25, 264, 305, 21, "Editor Command");
+        editor_command_input->tooltip("The editor command to open your external text editor.\nInclude any necessary \
+flags to ensure your editor does not background itself.\nExamples:\n    gvim -\
+f\n    gedit\n emacs");
+        editor_command_input->labelsize(10);
+        editor_command_input->textsize(10);
+        editor_command_input->callback((Fl_Callback*)cb_editor_command_input);
+        editor_command_input->align(Fl_Align(FL_ALIGN_TOP_LEFT));
+        editor_command_input->when(FL_WHEN_CHANGED);
+        fluid_prefs.get("external_editor_command", G_external_editor_command, "", sizeof(G_external_editor_command)-1);
+        editor_command_input->value(G_external_editor_command);
+      } // Fl_Input* editor_command_input
+      o->end();
+    } // Fl_Group* o
+    { Fl_Button* o = new Fl_Button(285, 320, 64, 25, "Close");
       o->tooltip("Close this dialog.");
       o->callback((Fl_Callback*)cb_Close1);
     } // Fl_Button* o
Index: Fl_Function_Type.cxx
===================================================================
--- Fl_Function_Type.cxx	(revision 11800)
+++ Fl_Function_Type.cxx	(working copy)
@@ -20,8 +20,10 @@
 #include <FL/Fl_Preferences.H>
 #include <FL/Fl_File_Chooser.H>
 #include "Fl_Type.h"
+#include "ExternalCodeEditor.h"
 #include <FL/fl_show_input.H>
 #include <FL/Fl_File_Chooser.H>
+#include "alignment_panel.h"
 #include "../src/flstring.h"
 #include <stdio.h>
 #include <stdlib.h>
@@ -434,6 +436,13 @@
 }
 
 void Fl_Code_Type::open() {
+  // Using an external code editor? Open it..
+  // TODO: Should probably fall back to builtin if we can't open ext editor -erco 07/07/16
+  if ( G_use_external_editor && G_external_editor_command[0] ) {
+    editor_.open_editor(G_external_editor_command, name());
+    return;
+  }
+  // Use built-in code editor..
   if (!code_panel) make_code_panel();
   const char *text = name();
   code_input->buffer()->text( text ? text : "" );
@@ -459,9 +468,21 @@
 
 Fl_Code_Type Fl_Code_type;
 
+void Fl_Code_Type::write() {
+  // See if any external editor has changes. If so, load changes into ram
+  // and update mtime/sizes before writing code.
+  editor_.changed_update();
+  Fl_Type::write();
+}
+
 void Fl_Code_Type::write_code1() {
+  // See if any external editor has changes. If so, load changes into ram
+  // and update mtime/sizes before writing code.
+  editor_.changed_update();
+
   const char* c = name();
   if (!c) return;
+
   const char *pch;
   const char *ind = indent();
   while( (pch=strchr(c,'\n')) )
Index: Makefile
===================================================================
--- Makefile	(revision 11800)
+++ Makefile	(working copy)
@@ -18,6 +18,7 @@
 
 CPPFILES = \
 	CodeEditor.cxx \
+	ExternalCodeEditor.cxx \
 	Fl_Function_Type.cxx \
 	Fl_Group_Type.cxx \
 	Fl_Menu_Type.cxx \
Index: alignment_panel.h
===================================================================
--- alignment_panel.h	(revision 11800)
+++ alignment_panel.h	(working copy)
@@ -27,6 +27,8 @@
 extern void load_history();
 extern void redraw_browser();
 extern int show_comments;
+extern int G_use_external_editor;
+extern char G_external_editor_command[512];
 extern int show_coredevmenus;
 extern struct Fl_Menu_Item *dbmanager_item;
 extern Fl_Preferences fluid_prefs;
@@ -73,6 +75,8 @@
 extern Fl_Check_Button *show_comments_button;
 #include <FL/Fl_Spinner.H>
 extern Fl_Spinner *recent_spinner;
+extern Fl_Check_Button *use_external_editor_button;
+extern Fl_Input *editor_command_input;
 Fl_Double_Window* make_settings_window();
 extern Fl_Menu_Item menu_scheme_choice[];
 extern Fl_Double_Window *shell_window;
Index: Fl_Type.cxx
===================================================================
--- Fl_Type.cxx	(revision 11800)
+++ Fl_Type.cxx	(working copy)
@@ -756,6 +756,7 @@
 int Fl_Type::is_menu_button() const {return 0;}
 int Fl_Type::is_group() const {return 0;}
 int Fl_Type::is_window() const {return 0;}
+int Fl_Type::is_code() const {return 0;}
 int Fl_Type::is_code_block() const {return 0;}
 int Fl_Type::is_decl_block() const {return 0;}
 int Fl_Type::is_comment() const {return 0;}
Index: fluid.cxx
===================================================================
--- fluid.cxx	(revision 11800)
+++ fluid.cxx	(working copy)
@@ -83,6 +83,9 @@
 #endif // HAVE_LIBPNG && HAVE_LIBZ
 }
 
+//
+// Globals..
+//
 static Fl_Help_Dialog *help_dialog = 0;
 
 Fl_Preferences	fluid_prefs(Fl_Preferences::USER, "fltk.org", "fluid");
@@ -91,6 +94,9 @@
 int snap = 1;
 int show_guides = 1;
 int show_comments = 1;
+int G_use_external_editor = 0;
+int G_debug = 0;
+char G_external_editor_command[512];
 int show_coredevmenus = 1;
 
 // File history info...
@@ -403,6 +409,7 @@
     delete help_dialog;
 
   undo_clear();
+  ExternalCodeEditor::tmpdir_clear();
 
   exit(0);
 }
@@ -1661,6 +1668,48 @@
   else save_item->deactivate();
 }
 
+// Timer to watch for external editor modifications
+//
+//    If one or more external editors are open, see if
+//    files they're editing have changed, and if so,
+//    load changes, update our record of the file's size + timestamp,
+//    and change the 'modified' state of the .fl file.
+//
+static void external_editor_timer(void*) {
+  int editors_open = ExternalCodeEditor::editors_open();
+  if ( G_use_external_editor && editors_open > 0 ) {
+    if ( G_debug ) printf("--- TIMER --- External editors open=%d\n", editors_open);
+    // Update the modify flag for the current fluid file
+    // if any of the open external editors have made changes
+    // since the last check.
+    //
+    // Walk the entire tree, check for modified files.
+    int modified = 0;
+    for (Fl_Type *p = Fl_Type::first; p; p = p->next) {
+      if ( p->is_code() ) {
+        Fl_Code_Type *code = (Fl_Code_Type*)p;
+        // Was code changed (e.g. by external editor)?
+        //   If so, reload and adjust our time stamp records
+        //   and keep track of how many files were modified.
+        //
+        if ( code->changed_update() ) {
+          modified++;
+        }
+        if ( code->is_editing() ) {     // editor open?
+          code->reap_editor();          // Try to reap it; maybe it recently closed
+        }
+      }
+    }
+    if ( modified ) set_modflag(1);     // any files modified? change modify flag
+    Fl::repeat_timeout(2.0, external_editor_timer);     // keep timer running
+  } else {
+    // No editors running? Disable update timer until one opens again
+    Fl::remove_timeout(external_editor_timer);
+    if ( G_debug ) printf("--- REMOVING UPDATE TIMEOUT\n");
+  }
+}
+
+
 ////////////////////////////////////////////////////////////////
 
 static int arg(int argc, char** argv, int& i) {
@@ -1711,7 +1760,6 @@
 }
 #endif
 
-
 int main(int argc,char **argv) {
   int i = 1;
   
@@ -1800,6 +1848,9 @@
   signal(SIGINT,sigint);
 #endif
 
+  // Setup timer for external editor updates
+  ExternalCodeEditor::set_update_timer_callback(external_editor_timer);
+
   grid_cb(horizontal_input, 0); // Makes sure that windows get snap params...
 
 #ifdef WIN32

--- ExternalCodeEditor.cxx	2016-07-07 23:07:09.749767500 -0700
+++ ExternalCodeEditor.cxx	2016-06-28 16:51:06.396865923 -0700
@@ -0,0 +1,5 @@
+#ifdef WIN32
+#include "ExternalCodeEditor_WIN32.cxx"
+#else
+#include "ExternalCodeEditor_UNIX.cxx"
+#endif
--- ExternalCodeEditor.h	2016-07-07 23:07:12.728767524 -0700
+++ ExternalCodeEditor.h	2016-07-07 21:49:42.527769853 -0700
@@ -0,0 +1,50 @@
+#ifndef _EXTCODEEDITOR_H
+#define _EXTCODEEDITOR_H
+
+#include <errno.h>      // errno
+#include <string.h>     // strerror()
+
+#include <sys/types.h>  // stat()..
+#include <sys/stat.h>
+#include <unistd.h>
+
+//
+// External code editor class for Unix
+//
+//     Handles starting and keeping track of an external text editor,
+//     including process start, temp file creation, bookkeeping, killing..
+//
+//         open_editor(cmd, code)  -- open external editor to edit 'code' as a tmp file
+//         is_editor()             -- tells caller if editor still open
+//         get_code()              -- returns current code from editor's tmp file
+//
+
+class ExternalCodeEditor {
+  int pid_;
+  const char *filename_;
+  time_t file_mtime_;                   // last modify time of the file (used to determine if file changed)
+  size_t file_size_;                    // last file size (used to determine if changed)
+protected:
+  void kill_editor();
+  size_t get_file_size() const;
+  const char *create_tmpdir();
+  const char *tmp_filename();
+  int make_args(char *s, int *aargc, char ***argv);
+  int start_editor(const char *cmd, const char *filename);
+public:
+  ExternalCodeEditor();
+  ~ExternalCodeEditor();
+  int is_editing();
+  pid_t reap_editor();
+  const char *filename() { return filename_; }
+  int open_editor(const char *editor_cmd, const char *code);
+  int get_code(const char **code);
+  int changed_update();
+  // Public static methods
+  static const char* tmpdir_name();
+  static void tmpdir_clear();
+  static int editors_open();
+  static void set_update_timer_callback(Fl_Timeout_Handler);
+};
+
+#endif
--- ExternalCodeEditor_UNIX.cxx	2016-07-07 23:07:15.718767516 -0700
+++ ExternalCodeEditor_UNIX.cxx	2016-07-07 21:51:05.651769830 -0700
@@ -0,0 +1,451 @@
+#include <errno.h>      // errno
+#include <string.h>     // strerror()
+#include <sys/types.h>  // stat()..
+#include <sys/stat.h>
+#include <fcntl.h>      // open()..
+#include <sys/wait.h>   // waitpid()..
+#include <signal.h>     // kill()..
+
+#include <unistd.h>
+#include <stdlib.h>     // free()
+#include <stdio.h>      // snprintf()..
+
+#include <FL/Fl.H>      // Fl_Timeout_Handler..
+#include <FL/fl_ask.H>  // fl_alert()
+
+#include "ExternalCodeEditor.h"
+
+// External code editor class for Unix
+
+extern int G_debug;     // defined in fluid.cxx
+
+// Static local internal bookkeeping of external code editor instances
+static ExternalCodeEditor **L_editors = 0;
+static int                  L_neditors = 0;
+static Fl_Timeout_Handler   L_update_timer_cb = 0;
+
+// Keep track of new editor instances
+static void new_editor(ExternalCodeEditor *e) {
+  L_editors = 
+    (ExternalCodeEditor**)realloc((void*)L_editors, (L_neditors+1) * sizeof(ExternalCodeEditor*));
+  L_editors[L_neditors++] = e;
+  if ( G_debug ) {
+    printf("Added editor %p\n", e);
+    for ( int t=0; t<L_neditors; t++ ) {
+      printf("L_editors[%d/%d]: %p\n", t, L_neditors, L_editors[t]);
+    }
+  }
+}
+
+// Keep track of editor destruction
+static void delete_editor(ExternalCodeEditor *e) {
+  if ( G_debug ) printf("Removing editor %p\n", e);
+  for ( int t=0; t<L_neditors; t++ ) {
+    if ( e == L_editors[t] ) {
+      while ( ++t<L_neditors ) L_editors[t-1] = L_editors[t];   // delete
+      --L_neditors;
+      break;
+    }
+  }
+  if ( G_debug ) {
+    for ( int t=0; t<L_neditors; t++ ) {
+      printf("L_editors[%d/%d]: %p\n", t, L_neditors, L_editors[t]);
+    }
+  }
+}
+
+// CTOR
+ExternalCodeEditor::ExternalCodeEditor() {
+  new_editor(this);
+  pid_        = -1;
+  filename_   = 0;
+  file_mtime_ = 0;
+  file_size_  = 0;
+}
+
+// DTOR
+ExternalCodeEditor::~ExternalCodeEditor() {
+  if ( G_debug )
+    printf("ExternalCodeEditor() DTOR CALLED (this=%p, pid=%ld)\n",
+           (void*)this, (long)pid_);
+  if ( pid_ != -1 ) kill_editor();
+  if ( filename_ ) { free((void*)filename_); } filename_ = 0;
+  delete_editor(this);
+}
+
+// Is editor running?
+int ExternalCodeEditor::is_editing() {
+  return( (pid_ != -1) ? 1 : 0 );
+}
+
+// Kill the running editor (if any)
+//   Kills the editor, reaps the process, and removes the tmp file.
+//   The dtor calls this to ensure no editors remain running when fluid exits.
+//
+void ExternalCodeEditor::kill_editor() {
+  if ( G_debug ) printf("kill_editor() called: pid=%ld\n", (long)pid_);
+  if ( pid_ == -1 ) return;     // editor not running? return..
+  kill(pid_, SIGTERM);          // kill editor
+  while ( pid_ != -1 ) {        // and wait for it to finish..
+    switch (reap_editor()) {
+      case -1:  // error
+        fl_alert("Can't seem to close editor of file: %s\n"
+                 "waitpid() returned: %s\n"
+                 "Please close editor and hit OK",
+                 filename_,
+                 strerror(errno));
+        continue;
+      case 0:   // process still running
+        fl_alert("Can't seem to close editor of file: %s\n"
+                 "Please close editor and hit OK",
+                 filename_);
+        continue;
+      default:  // process reaped
+        if ( G_debug ) printf("*** REAPED KILLED EXTERNAL EDITOR: PID %ld\n", (long)pid_);
+        pid_ = -1;
+        break;
+    }
+  }
+  return;
+}
+
+// Return editor's file size in bytes
+size_t ExternalCodeEditor::get_file_size() const {
+  struct stat sbuf;
+  if ( stat(filename_, &sbuf) < 0 ) {
+    fl_alert("ERROR: can't get file size for '%s': %s",
+      filename_, strerror(errno));
+    return(0);
+  }
+  return sbuf.st_size;
+}
+
+// See if file changed since last check, and update records if so.
+//
+// OPTIMIZATION: Let's ignore changes made within the last 2 seconds,
+// to give the editor time to fully write out the file.
+//
+// Returns:
+//     1 -- file changed, internal records updated
+//     0 -- file unchanged
+//    -1 -- error getting file info (strerror() has reason)
+//
+int ExternalCodeEditor::changed_update() {
+  // Get current time/size info
+  struct stat sbuf;
+  if ( stat(filename_, &sbuf) < 0 ) return(-1);
+  time_t now_mtime = sbuf.st_mtime;
+  size_t now_size  = sbuf.st_size;
+  // Changed? Update records, return true
+  int changed = 0;
+  if ( now_mtime != file_mtime_ ) { changed = 1; file_mtime_ = now_mtime; }
+  if ( now_size  != file_size_  ) { changed = 1; file_size_  = now_size; }
+  return changed;
+}
+
+// See if file changed since last check, update mtime/size if so
+//    Returns 1 if so (internal info updated), 0 if not.
+//
+
+// See if file exists
+static int is_file(const char *filename) {
+  struct stat buf;
+  if ( stat(filename, &buf) < 0 ) return(0);
+  return(S_ISREG(buf.st_mode) ? 1 : 0); // regular file?
+}
+
+// See if dir exists
+static int is_dir(const char *dirname) {
+  struct stat buf;
+  if ( stat(dirname, &buf) < 0 ) return(0);
+  return(S_ISDIR(buf.st_mode) ? 1 : 0); // a dir?
+}
+
+// [STATIC] Return tmpdir name for this fluid instance
+//     Returns a pointer to static memory.
+//
+//     This is static so the pathname can be accessed
+//     outside of the context of a particular instance.
+//
+const char* ExternalCodeEditor::tmpdir_name() {
+  static char dirname[100];
+  snprintf(dirname, sizeof(dirname), "/tmp/.fluid-%ld", (long)getpid());
+  return dirname;
+}
+
+// [STATIC] Clear the external editor's tempdir
+//    This is static so that the main program can call it
+//    on exit to clean up.
+//
+void ExternalCodeEditor::tmpdir_clear() {
+  const char *tmpdir = tmpdir_name();
+  // Remove all editor tmpfiles
+  for ( int t=0; t<L_neditors; t++ ) {
+    const char *tmpfile = L_editors[t]->filename();
+    if ( tmpfile && is_file(tmpfile) ) {
+      if ( G_debug ) printf("Removing tmpfile '%s'\n", tmpfile);
+      if ( remove(tmpfile) < 0 ) {
+        fl_alert("WARNING: Can't remove() '%s': %s", tmpfile, strerror(errno));
+      }
+    }
+  }
+  // tmpdir should now be empty; remove it
+  if ( is_dir(tmpdir) ) {
+    if ( G_debug ) printf("Removing tmpdir '%s'\n", tmpdir);
+    if ( rmdir(tmpdir) < 0 ) {
+      fl_alert("WARNING: Can't rmdir() '%s': %s", tmpdir, strerror(errno));
+    }
+  }
+}
+
+// Returns static temp dirname
+//    Returns NULL if can't, posts dialog explaining why
+//
+const char* ExternalCodeEditor::create_tmpdir() {
+  const char *dirname = tmpdir_name();
+  if ( ! is_dir(dirname) ) {
+    if ( mkdir(dirname, 0777) < 0 ) {
+      fl_alert("can't create directory '%s': %s",
+        dirname, strerror(errno));
+      return NULL;
+    }
+  }
+  return dirname;
+}
+
+// Returns temp filename that caller must free() when done.
+//    Returns NULL if can't, posts dialog explaining why
+//
+const char* ExternalCodeEditor::tmp_filename() {
+  char path[512];
+  const char *tmpdir = create_tmpdir();
+  if ( ! tmpdir ) return 0;
+  const char *ext = "cxx";             // TODO: get this from fluid? -erco
+  sprintf(path, "%s/%p.%s", tmpdir, (void*)this, ext);
+  return strdup(path);
+}
+
+// Save string 'code' to 'filename'
+// Returns:
+//    0 on success
+//   -1 on error (posts dialog with reason)
+//
+static int save_file(const char *filename, const char *code) {
+  int fd = open(filename, O_WRONLY|O_CREAT, 0666);
+  if ( fd == -1 ) {
+    fl_alert("ERROR: can't open() '%s': %s",
+      filename, strerror(errno));
+    return -1;
+  }
+  size_t clen = strlen(code);
+  ssize_t count = write(fd, code, clen);
+  if ( count == -1 ) {
+    fl_alert("ERROR: write() '%s': %s",
+      filename, strerror(errno));
+    close(fd);
+    return -1;
+  }
+  if ( (long)count != (long)clen ) {
+    fl_alert("ERROR: write() '%s': wrote only %lu bytes, expected %lu",
+             filename,
+             (unsigned long)clen,
+             (unsigned long)count);
+    close(fd);
+    return -1;
+  }
+  close(fd);
+  return 0;
+}
+
+// Convert string 's' to array of argv[], useful for execve()
+//     o 's' will be modified (words will be NULL separated)
+//     o argv[] will end up pointing to the words of 's'
+//     o Caller must free argv with: free(argv);
+//
+int ExternalCodeEditor::make_args(char *s,         // string containing words (gets trashed!)
+                                  int *aargc,      // pointer to argc
+                                  char ***aargv) { // pointer to argv
+  char *ss, **argv;
+  if ((argv=(char**)malloc(sizeof(char*) * (strlen(s)/2)))==NULL) {
+    return -1;
+  }
+  int t;
+  for(t=0; (t==0)?(ss=strtok(s," \t")):(ss=strtok(0," \t")); t++) {
+    argv[t] = ss;
+  }
+  argv[t] = 0;
+  aargv[0] = argv;
+  aargc[0] = t;
+  return(t);
+}
+
+// Fire off editor in background (fork/exec)
+// Returns:
+//    >  0 on success, leaves editor child process running as 'pid_'
+//    > -1 on error, posts dialog with reason (child exits)
+//
+int ExternalCodeEditor::start_editor(const char *editor_cmd,
+                                     const char *filename) {
+  if ( G_debug )
+    printf("start_editor() cmd='%s', filename='%s'\n", editor_cmd, filename);
+  switch ( pid_ = fork() ) {
+    case -1:    // error
+      fl_alert("couldn't fork(): %s", strerror(errno));
+      return -1;
+    case 0: {   // child
+      char cmd[1024];
+      snprintf(cmd, sizeof(cmd), "%s %s", editor_cmd, filename);
+      {
+        int nargs;
+        char **args;
+        make_args(cmd, &nargs, &args);
+        execvp(args[0], args);  // run the command, doesn't return if succeeded
+      }
+      fl_alert("couldn't exec() '%s': %s", cmd, strerror(errno));
+      exit(1);
+    }
+    default:    // parent
+      if ( G_debug ) printf("PARENT: pid_=%ld\n", (long)pid_);
+      break;
+  }
+  return 0;
+}
+
+// TRY TO REAP THIS EDITOR'S PID
+// Returns:
+//   -2 -- editor not open
+//   -1 -- waitpid() failed (errno has reason)
+//    0 -- process still running
+//   >0 -- process finished + reaped (value is pid)
+//
+pid_t ExternalCodeEditor::reap_editor() {
+  if ( pid_ == -1 ) return -2;
+  int status = 0;
+  pid_t wpid;
+  switch (wpid = waitpid(pid_, &status, WNOHANG)) {
+    case -1:    // waitpid() failed
+      return -1;
+    case 0:     // process still running
+      return 0;
+    default:    // process reaped
+      pid_ = -1;
+      break;
+  }
+  if ( G_debug ) 
+    printf("*** Editor process reaped: pid=%ld\n", long(wpid));
+  return wpid;
+}
+
+// [Public] Open external editor using 'editor_cmd' to edit 'code'
+//    'code' contains multiline code to be edited as a temp file.
+//    Returns:
+//        0 if succeeds
+//       -1 if can't open editor (already open, etc),
+//          errors were shown to user in a dialog
+//
+int ExternalCodeEditor::open_editor(const char *editor_cmd,
+                                    const char *code) {
+  // See if tmpfile already exists or editor already open
+  if ( !filename_ ) {
+    filename_ = tmp_filename();
+  }
+  if ( is_file(filename_) ) {
+    if ( pid_ != -1 ) {
+      // See if editor closed but not reaped; try to reap
+      pid_t wpid = reap_editor();
+      switch (wpid) {
+        case -1:        // waitpid() failed
+          fl_alert("ERROR: waitpid() failed: %s\nfile='%s', pid=%ld",
+            strerror(errno), filename_, (long)pid_);
+          return -1;
+        case 0:         // process still running
+          fl_alert("ERROR: editor already open: '%s' (PID=%ld)",
+            filename_, (long)pid_);
+          return 0;
+        default:        // process reaped, wpid is pid reaped
+          if ( G_debug )
+            printf("*** REAPED KILLED EXTERNAL EDITOR: PID %ld / %ld\n",
+                   (long)wpid, (long)pid_);
+          pid_ = -1;    // reaped: old pid now invalid
+          break;        // fall thru to open new editor instance
+      }
+    }
+  }
+  if ( save_file(filename_, code) < 0 ) {          // save 's' to file
+    return -1;  // errors were shown in dialog
+  }
+  // Update mtime/size from closed file
+  struct stat sbuf;
+  if ( stat(filename_, &sbuf) < 0 ) {
+    fl_alert("ERROR: can't stat('%s'): %s", filename_, strerror(errno));
+    return -1;
+  }
+  file_mtime_ = sbuf.st_mtime;
+  file_size_  = sbuf.st_size;
+  if ( start_editor(editor_cmd, filename_) < 0 ) { // open file in external editor
+    if ( G_debug ) printf("Editor failed to start\n");
+    return -1;  // errors were shown in dialog
+  }
+  if ( G_debug ) printf("Editor started as pid=%ld\n", (long)pid_);
+  // New editor opened -- start update timer (if not already)
+  if ( L_update_timer_cb && !Fl::has_timeout(L_update_timer_cb) ) {
+    if ( G_debug ) printf("--- EDITOR OPENED: STARTING UPDATE TIMER\n");
+    Fl::add_timeout(2.0, L_update_timer_cb);
+  }
+  return 0;
+}
+
+// [Public] Get the current source code from the editor session 
+// Returns:
+//    0 on success, 'code' will be a pointer to malloc()'ed string that caller must free().
+//   -1 on error, 'code' is NULL, dialog was posted to user explaining reason
+//   -2 if no editor is open, 'code' is NULL, no dialog posted
+//
+int ExternalCodeEditor::get_code(const char **code) {
+  code[0] = 0;
+  size_t size = get_file_size();
+  if ( size == 0 ) return(-1);  // temp file disappeared?
+  int fd = open(filename_, O_RDONLY);
+  if ( fd < 0 ) {
+    fl_alert("ERROR: can't open '%s': %s", filename_, strerror(errno));
+    return -1;
+  }
+  char *buf = (char*)malloc(size + 1);
+  ssize_t count = read(fd, buf, size);
+  if ( count == -1 ) {
+    fl_alert("ERROR: read() %s: %s", filename_, strerror(errno));
+    free((void*)buf);
+    return -1;
+  }
+  if ( (long)count != (long)size ) {
+    fl_alert("WARNING: read() failed for %s:\nexpected %lu bytes, only got %lu",
+             filename_,
+             (unsigned long)size,
+             (unsigned long)count);
+  }
+  close(fd);
+  buf[count] = '\0';
+  code[0] = buf;        // return pointer to allocated buffer
+  return(0);
+}
+
+// [Public/Static]
+//     See if any external editors are open.
+//     Can be used by app's modify timer to determine if there are any editors
+//     to be checked..
+//
+int ExternalCodeEditor::editors_open() {
+  if ( L_neditors == 0 ) return 0;
+  int count = 0;
+  for ( int i=0; i<L_neditors; i++ ) {
+    if ( L_editors[i]->is_editing() ) {
+      ++count;
+    }
+  }
+  return count;
+}
+
+// [Public/Static]
+void ExternalCodeEditor::set_update_timer_callback(Fl_Timeout_Handler cb) {
+  L_update_timer_cb = cb;
+}
--- ExternalCodeEditor_UNIX.h	2016-07-07 23:07:19.219767495 -0700
+++ ExternalCodeEditor_UNIX.h	2016-06-28 15:17:56.673868733 -0700
@@ -0,0 +1,108 @@
+#include <errno.h>      // errno
+#include <string.h>     // strerror()
+
+#include <sys/types.h>  // stat()..
+#include <sys/stat.h>
+#include <unistd.h>
+
+//
+// External code editor class for Unix
+//
+//     Handles starting and keeping track of an external text editor,
+//     including process start, temp file creation, bookkeeping, killing..
+//
+//         open_editor(cmd, code)  -- open external editor to edit 'code' as a tmp file
+//         is_editor()             -- tells caller if editor still open
+//         get_code()              -- returns current code from editor's tmp file
+//
+
+class ExternalCodeEditor {
+  int pid;
+  const char *filename;
+
+  // PROTECTED METHODS
+protected:
+  // Kill the running editor
+  void kill_editor();
+  size_t get_file_size() const;
+  int exists(const char *filename);
+  const char *create_tmpdir();
+  const char *tmpfilename();
+  int save_file(const char *filename, const char *code);
+  int make_args(char *s, int *aargc, char ***argv);
+  int start_editor(const char *cmd, const char *filename);
+public:
+  // CTOR
+  ExternalCodeEditor(const char *tmpname) {
+    pid = -1;
+    filename = strdup(tmpname);
+  }
+  // DTOR
+  ~ExternalCodeEditor() {
+    if ( pid != -1 ) kill_editor();
+    if ( tmpname ) { free(tmpname); } tmpname = 0;
+  }
+  // Is editor running?
+  int is_editor() {
+    return( (pid != -1) ? 1 : 0 );
+  }
+  // Return tmp filename being edited (NULL means editor is not open)
+  const char *getfilename() {
+    return filename;
+  }
+  int open_editor(const char *editor_cmd, const char *code);
+  int get_code(const char **code);
+};
+
+//
+// When user double-clicks code, the external editor is opened (if not already).
+// If the file is already open in an external editor, an fl_alert() tells you the
+// filename (presumably so you can find it by the window title)
+//
+// When an editor is closed, the PID should be reaped, edited temp file
+// loaded into ram, and temp file then removed. 
+//
+// The closing editor doesn't have to be reaped /immediately/,
+// since any kind of save or quit can check for the temp file
+// and if it exists, check if the pid exited, and if so, reap it.
+//
+// So when e.g. a Save() is done, after grabbing temp file into ram,
+// if pid reaps (using waitpid()), remove the temp file.
+//
+// TEMP FILE
+//     Create a temp dir for this instance of fluid (e.g. /tmp/.fluid-<PID>)
+//     and save all related files there.
+//
+//     Files could have the name of the app, and the instance number
+//     of the ExternalCodeEditor, e.g. /tmp/.fluid-<PID>/foo-123
+//     Don't use the name of the function, as the user might change that,
+//     and we don't want to have re-open the editor just because the temp file name changed.
+//
+// FLUID OPENS .fl FILE
+//     Close all existing open editors (kill them) if any
+//     Create editor instances, but don't open the editors 
+//     (even if saved open).
+//
+// FLUID SAVES OR INVOKES "WRITE CODE"
+//     The code instances are recursively called to generate code.
+//     Each one will open the file being edited and load it into ram
+//     before generating code.
+//
+// FLUID USED TO OPEN AN EDITOR
+//    Make sure editor doesn't have an unreaped temp file before
+//    opening a new editor. If it does, load it FIRST, then invoke
+//    the 'open editor' code, which should:
+//
+//        a) increase the instance number for the editor
+//        b) Write the code to a new temp file
+//        c) open a bg process that edits the file
+//        d) Keep the pid / process handle so it can be reaped
+//        e) Start a thread (if not already) to reap it.
+//
+// SOMEONE CLOSES AN EDITOR
+//    The app's wait() thread should immediately reap the process 
+//    and send a message to fluid indicating the editor was closed
+//    and it should load the temp file, remove it, and reset the
+//    ExternalCodeEditor's state to 'not editing'.
+//
+//
