Index: fluid/Fl_Type.h
===================================================================
--- fluid/Fl_Type.h	(revision 11804)
+++ fluid/Fl_Type.h	(working copy)
@@ -32,6 +32,12 @@
 #include <FL/fl_draw.H>
 #include <stdarg.h>
 
+#ifdef WIN32
+  #include "ExternalCodeEditor_WIN32.h"
+#else
+  #include "ExternalCodeEditor_UNIX.h"
+#endif
+
 void set_modflag(int mf);
 
 class Fl_Type {
@@ -109,7 +115,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 +149,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 +192,56 @@
 };
 
 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 -- wait failed
+  //    0 -- process still running
+  //   >0 -- process finished + reaped (returns pid)
+  //
+  int reap_editor() {
+    return editor_.reap_editor();
+  }
+  // Handle external editor file modifications
+  // If changed, record keeping is updated and file's contents is loaded into ram
+  //
+  // Returns:
+  //     0 -- file unchanged or not editing
+  //     1 -- file changed, internal records updated, 'code' has new content
+  //    -1 -- error getting file info (get_ms_errmsg() has reason)
+  //
+  // TODO: Figure out how saving a fluid file can be intercepted to grab 
+  //       current contents of editor file..
+  //
+  int handle_editor_changes() {
+    const char *newcode = 0;
+    switch ( editor_.handle_changes(&newcode) ) {
+      case 1: {            // (1)=changed
+        name(newcode);     // update value in ram
+        free((void*)newcode);
+        return 1;
+      }
+      case -1: return -1;  // (-1)=error -- couldn't read file (dialog showed reason)
+      default: break;      // (0)=no change
+    }
+    return 0;
+  }
 };
 
 class Fl_CodeBlock_Type : public Fl_Type {
Index: fluid/ExternalCodeEditor_WIN32.h
===================================================================
--- fluid/ExternalCodeEditor_WIN32.h	(revision 0)
+++ fluid/ExternalCodeEditor_WIN32.h	(revision 0)
@@ -0,0 +1,48 @@
+//
+// "$Id: ExternalCodeEditor_WIN32.h 10800 2016-07-10 00:00:00Z greg.ercolano $".
+//
+//       External code editor management class for Windows
+//
+//       Handles starting and keeping track of an external text editor,
+//       including process start, temp file creation/removal, bookkeeping, killing..
+//
+#ifndef _EXTCODEEDITOR_H
+#define _EXTCODEEDITOR_H
+
+#include <windows.h>                /* CreateFile().. */
+#include <string.h>                 /* sprintf().. */
+
+class ExternalCodeEditor {
+  PROCESS_INFORMATION pinfo_;       // CreateProcess() handle to running process 
+  FILETIME            file_mtime_;  // last modify time of the file (used to determine if file changed)
+  LARGE_INTEGER       file_size_;   // last file size (used to determine if changed)
+  const char *        filename_;    // tmpfilename editor uses
+protected:
+  void kill_editor();
+  void reap_cleanup();
+  const char *create_tmpdir();
+  const char *tmp_filename();
+  int start_editor(const char *cmd, const char *filename);
+  void set_filename(const char *val);
+public:
+  ExternalCodeEditor();
+  ~ExternalCodeEditor();
+  int is_editing();
+  DWORD reap_editor();
+  const char *filename() { return filename_; }
+  int open_editor(const char *editor_cmd, const char *code);
+  int handle_changes(const char **code, int force=0);
+  int remove_tmpfile();
+  // Public static methods
+  static void start_update_timer();
+  static void stop_update_timer();
+  static const char* tmpdir_name();
+  static void tmpdir_clear();
+  static int editors_open();
+  static void set_update_timer_callback(Fl_Timeout_Handler);
+};
+
+#endif /*_EXTCODEEDITOR_H */
+//
+// End of "$Id: ExternalCodeEditor_WIN32.h 10800 2016-07-10 00:00:00Z greg.ercolano $".
+//
Index: fluid/alignment_panel.fl
===================================================================
--- fluid/alignment_panel.fl	(revision 11804)
+++ fluid/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
 }
 
@@ -161,7 +167,8 @@
 Function {make_settings_window()} {} {
   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 resizable
+    code0 {o->size_range(o->w(), o->h());} non_modal visible
   } {
     Fl_Choice scheme_choice {
       label {Scheme: }
@@ -194,7 +201,7 @@
       }
     }
     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: }
@@ -254,10 +261,38 @@
       code2 {recent_spinner->maximum(10);}
       code3 {recent_spinner->value(c);}
     }
+    Fl_Group {} {open
+      xywh {10 210 337 95} box THIN_UP_BOX color 47 resizable
+    } {
+      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();} selected
+        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 12 align 5 when 1 textsize 12
+        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}
+      tooltip {Close this dialog.} xywh {285 320 64 25}
     }
   }
 }
Index: fluid/alignment_panel.cxx
===================================================================
--- fluid/alignment_panel.cxx	(revision 11804)
+++ fluid/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");
+  { Fl_Double_Window* o = 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,7 +230,7 @@
       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: ");
@@ -271,10 +288,36 @@
       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(12);
+        editor_command_input->textsize(12);
+        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::current()->resizable(o);
+    } // 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
+    o->size_range(o->w(), o->h());
     settings_window->set_non_modal();
     settings_window->end();
   } // Fl_Double_Window* settings_window
Index: fluid/Fl_Function_Type.cxx
===================================================================
--- fluid/Fl_Function_Type.cxx	(revision 11804)
+++ fluid/Fl_Function_Type.cxx	(working copy)
@@ -15,17 +15,25 @@
 //
 //     http://www.fltk.org/str.php
 //
-
 #include <FL/Fl.H>
+#include <FL/Fl_Window.H>
 #include <FL/Fl_Preferences.H>
 #include <FL/Fl_File_Chooser.H>
 #include "Fl_Type.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>
+#include <string.h>
 
+#ifdef WIN32
+  #include "ExternalCodeEditor_WIN32.h"
+#else
+  #include "ExternalCodeEditor_UNIX.h"
+#endif
+
 extern int i18n_type;
 extern const char* i18n_include;
 extern const char* i18n_function;
@@ -38,6 +46,7 @@
 extern void redraw_browser();
 extern void goto_source_dir();
 extern void leave_source_dir();
+extern Fl_Window *main_window;
 
 ////////////////////////////////////////////////////////////////
 // quick check of any C code for legality, returns an error message
@@ -434,6 +443,14 @@
 }
 
 void Fl_Code_Type::open() {
+  // Using an external code editor? Open it..
+  if ( G_use_external_editor && G_external_editor_command[0] ) {
+    const char *cmd = G_external_editor_command;
+    const char *code = name();
+    if ( editor_.open_editor(cmd, code) == 0 )
+      return;   // return if editor opened ok, fallthru to built-in if not
+  }
+  // Use built-in code editor..
   if (!code_panel) make_code_panel();
   const char *text = name();
   code_input->buffer()->text( text ? text : "" );
@@ -459,9 +476,23 @@
 
 Fl_Code_Type Fl_Code_type;
 
+void Fl_Code_Type::write() {
+  // External editor changes? If so, load changes into ram, update mtime/size
+  if ( handle_editor_changes() == 1 ) {
+    main_window->redraw();    // tell fluid to redraw; edits may affect tree's contents
+  }
+  Fl_Type::write();
+}
+
 void Fl_Code_Type::write_code1() {
+  // External editor changes? If so, load changes into ram, update mtime/size
+  if ( handle_editor_changes() == 1 ) {
+    main_window->redraw();    // tell fluid to redraw; edits may affect tree's contents
+  }
+
   const char* c = name();
   if (!c) return;
+
   const char *pch;
   const char *ind = indent();
   while( (pch=strchr(c,'\n')) )
Index: fluid/Makefile
===================================================================
--- fluid/Makefile	(revision 11804)
+++ fluid/Makefile	(working copy)
@@ -18,6 +18,8 @@
 
 CPPFILES = \
 	CodeEditor.cxx \
+	ExternalCodeEditor_UNIX.cxx \
+	ExternalCodeEditor_WIN32.cxx \
 	Fl_Function_Type.cxx \
 	Fl_Group_Type.cxx \
 	Fl_Menu_Type.cxx \
Index: fluid/alignment_panel.h
===================================================================
--- fluid/alignment_panel.h	(revision 11804)
+++ fluid/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: fluid/ExternalCodeEditor_UNIX.cxx
===================================================================
--- fluid/ExternalCodeEditor_UNIX.cxx	(revision 0)
+++ fluid/ExternalCodeEditor_UNIX.cxx	(revision 0)
@@ -0,0 +1,435 @@
+//
+// "$Id: ExternalCodeEditor_UNIX.cxx 10800 2016-07-10 00:00:00Z greg.ercolano $".
+//
+//       External code editor management class for Unix
+//
+#ifndef WIN32           /* This entire file unix only */
+
+#include <errno.h>      /* errno */
+#include <string.h>     /* strerror() */
+#include <sys/types.h>  /* stat().. */
+#include <sys/stat.h>
+#include <sys/wait.h>   /* waitpid().. */
+#include <fcntl.h>      /* open().. */
+#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_UNIX.h"
+
+extern int G_debug;     // defined in fluid.cxx
+
+// Static local data
+static int L_editors_open = 0;                          // keep track of #editors open
+static Fl_Timeout_Handler L_update_timer_cb = 0;        // app's update timer callback
+
+// [Static/Local] 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?
+}
+
+// [Static/Local] 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?
+}
+
+// CTOR
+ExternalCodeEditor::ExternalCodeEditor() {
+  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_);
+  kill_editor();    // Kill open editor, deletes tmp file
+  set_filename(0);  // free()s filename
+}
+
+// [Protected] Set the filename. Handles memory allocation/free
+//     If set to NULL, frees memory.
+//
+void ExternalCodeEditor::set_filename(const char *val) {
+  if ( filename_ ) free((void*)filename_);
+  filename_ = val ? strdup(val) : 0;
+}
+
+// [Public] Is editor running?
+int ExternalCodeEditor::is_editing() {
+  return( (pid_ != -1) ? 1 : 0 );
+}
+
+// [Protected] 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 ( !is_editing() ) return;  // editor not running? return..
+  kill(pid_, SIGTERM);          // kill editor
+  int wcount = 0;
+  while ( pid_ != -1 ) {        // and wait for it to finish..
+    usleep(100000);             // 1/10th sec delay gives editor time to close itself
+    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
+        if ( ++wcount > 3 ) {   // retry 3x with 1/10th delay before showing dialog
+          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;
+}
+
+// [Public] Handle if file changed since last check, and update records if so.
+// Load new data into 'code', which caller must free().
+// If 'force' set, forces reload even if file size/time didn't change.
+//
+// Returns:
+//     0 -- file unchanged or not editing
+//     1 -- file changed, internal records updated, 'code' has new content
+//    -1 -- error getting file info (strerror() has reason)
+//
+int ExternalCodeEditor::handle_changes(const char **code, int force) {
+  code[0] = 0;
+  if ( !is_editing() ) return 0;
+  // Get current time/size info, see if file changed
+  int changed = 0;
+  {
+    struct stat sbuf;
+    if ( stat(filename(), &sbuf) < 0 ) return(-1);  // TODO: show fl_alert(), do this in win32 too, adjust func call docs above
+    time_t now_mtime = sbuf.st_mtime;
+    size_t now_size  = sbuf.st_size;
+    // OK, now see if file changed; update records if so
+    if ( now_mtime != file_mtime_ ) { changed = 1; file_mtime_ = now_mtime; }
+    if ( now_size  != file_size_  ) { changed = 1; file_size_  = now_size; }
+  }
+  // No changes? done
+  if ( !changed && !force ) return 0;
+  // Changes? Load file, and fallthru to close()
+  int fd = open(filename(), O_RDONLY);
+  if ( fd < 0 ) {
+    fl_alert("ERROR: can't open '%s': %s", filename(), strerror(errno));
+    return -1;
+  }
+  int ret = 0;
+  char *buf = (char*)malloc(file_size_ + 1);
+  ssize_t count = read(fd, buf, file_size_);
+  if ( count == -1 ) {
+    fl_alert("ERROR: read() %s: %s", filename(), strerror(errno));
+    free((void*)buf);
+    ret = -1;
+  } else if ( (long)count != (long)file_size_ ) {
+    fl_alert("ERROR: read() failed for %s:\n"
+             "expected %ld bytes, only got %ld",
+             filename(), long(file_size_), long(count));
+    ret = -1;
+  } else {
+    // Success -- file loaded OK
+    buf[count] = '\0';
+    code[0] = buf;        // return pointer to allocated buffer
+    ret = 1;
+  }
+  close(fd);
+  return ret;
+}
+
+// [Public] Remove the tmp file (if it exists), and zero out filename/mtime/size
+// Returns:
+//    -1 -- on error (dialog is posted as to why)
+//     0 -- no file to remove
+//     1 -- file was removed
+//
+int ExternalCodeEditor::remove_tmpfile() {
+  const char *tmpfile = filename();
+  if ( !tmpfile ) return 0;
+  // Filename set? remove (if exists) and zero filename/mtime/size
+  if ( 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));
+      return -1;
+    }
+  }
+  set_filename(0);
+  file_mtime_ = 0;
+  file_size_  = 0;
+  return 1;
+}
+
+// [Static/Public] Return tmpdir name for this fluid instance.
+//     Returns pointer to static memory.
+//
+const char* ExternalCodeEditor::tmpdir_name() {
+  static char dirname[100];
+  snprintf(dirname, sizeof(dirname), "/tmp/.fluid-%ld", (long)getpid());
+  return dirname;
+}
+
+// [Static/Public] Clear the external editor's tempdir
+//    Static so that the main program can call it on exit to clean up.
+//
+void ExternalCodeEditor::tmpdir_clear() {
+  const char *tmpdir = tmpdir_name();
+  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));
+    }
+  }
+}
+
+// [Protected] Creates temp dir (if doesn't exist) and returns the dirname
+// as a static string. Returns NULL on error, dialog shows reason.
+//
+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;
+}
+
+// [Protected] Returns temp filename in static buffer.
+//    Returns NULL if can't, posts dialog explaining why.
+//
+const char* ExternalCodeEditor::tmp_filename() {
+  static char path[512];
+  const char *tmpdir = create_tmpdir();
+  if ( !tmpdir ) return 0;
+  extern const char *code_file_name;   // fluid's global
+  const char *ext  = code_file_name;   // e.g. ".cxx"
+  snprintf(path, sizeof(path), "%s/%p%s", tmpdir, (void*)this, ext);
+  path[sizeof(path)-1] = 0;
+  return 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: open() '%s': %s", filename, strerror(errno));
+    return -1;
+  }
+  ssize_t clen = strlen(code);
+  ssize_t count = write(fd, code, clen);
+  int ret = 0;
+  if ( count == -1 ) {
+    fl_alert("ERROR: write() '%s': %s", filename, strerror(errno));
+    ret = -1; // fallthru to close()
+  } else if ( count != clen ) {
+    fl_alert("ERROR: write() '%s': wrote only %lu bytes, expected %lu",
+             filename, (unsigned long)count, (unsigned long)clen);
+    ret = -1; // fallthru to close()
+  }
+  close(fd);
+  return(ret);
+}
+
+// [Static/Local] 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);
+//
+static int 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);
+}
+
+// [Protected] Start 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);
+  char cmd[1024];
+  snprintf(cmd, sizeof(cmd), "%s %s", editor_cmd, filename);
+  // Fork editor to background..
+  switch ( pid_ = fork() ) {
+    case -1:    // error
+      fl_alert("couldn't fork(): %s", strerror(errno));
+      return -1;
+    case 0: {   // child
+      // NOTE: OSX wants minimal code between fork/exec, see Apple TN2083
+      int nargs;
+      char **args = 0;
+      make_args(cmd, &nargs, &args);
+      execvp(args[0], args);  // run command - doesn't return if succeeds
+      fl_alert("couldn't exec() '%s': %s", cmd, strerror(errno));
+      exit(1);
+    }
+    default:    // parent
+      if ( L_editors_open++ == 0 )  // first editor? start timers
+        { start_update_timer(); }
+      if ( G_debug )
+        printf("--- EDITOR STARTED: pid_=%ld #open=%d\n", (long)pid_, L_editors_open);
+      break;
+  }
+  return 0;
+}
+
+// [Public] Try to reap external editor process
+// Returns:
+//   -2 -- editor not open
+//   -1 -- waitpid() failed (errno has reason)
+//    0 -- process still running
+//   >0 -- process finished + reaped (value is pid)
+//         Handles removing tmpfile/zeroing file_mtime/file_size
+//
+pid_t ExternalCodeEditor::reap_editor() {
+  if ( !is_editing() ) return -2;
+  int status = 0;
+  pid_t wpid;
+  switch (wpid = waitpid(pid_, &status, WNOHANG)) {
+    case -1:    // waitpid() failed
+      return -1;
+    case 0:     // process didn't reap, still running
+      return 0;
+    default:    // process reaped
+      remove_tmpfile(); // also zeroes mtime/size
+      pid_ = -1;
+      if ( --L_editors_open <= 0 )
+        { stop_update_timer(); }
+      break;
+  }
+  if ( G_debug ) 
+    printf("*** EDITOR REAPED: pid=%ld #open=%d\n", long(wpid), L_editors_open);
+  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) {
+  // Make sure a temp filename exists
+  if ( !filename() ) {
+    set_filename(tmp_filename());
+    if ( !filename() ) return -1;
+  }
+  // See if tmpfile already exists or editor already open
+  if ( is_file(filename()) ) {
+    if ( is_editing() ) {
+      // See if editor recently 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("Editor Already Open\n  file='%s'\n  pid=%ld",
+            filename(), (long)pid_);
+          return 0;
+        default:        // process reaped, wpid is pid reaped
+          if ( G_debug )
+            printf("*** REAPED EXTERNAL EDITOR: PID %ld\n", (long)wpid);
+          break;        // fall thru to open new editor instance
+      }
+      // Reinstate tmp filename (reap_editor() clears it)
+      set_filename(tmp_filename());
+    }
+  }
+  if ( save_file(filename(), code) < 0 ) {
+    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
+  }
+  return 0;
+}
+
+// [Public/Static] Start update timer
+void ExternalCodeEditor::start_update_timer() {
+  if ( !L_update_timer_cb ) return;
+  if ( G_debug ) printf("--- TIMER: STARTING UPDATES\n");
+  Fl::add_timeout(2.0, L_update_timer_cb);
+}
+
+// [Public/Static] Stop update timer
+void ExternalCodeEditor::stop_update_timer() {
+  if ( !L_update_timer_cb ) return;
+  if ( G_debug ) printf("--- TIMER: STOPPING UPDATES\n");
+  Fl::remove_timeout(L_update_timer_cb);
+}
+
+// [Public/Static] Set app's external editor update timer callback
+//   This is the app's callback callback we start while editors are open,
+//   and stop when all editors are closed.
+//
+void ExternalCodeEditor::set_update_timer_callback(Fl_Timeout_Handler cb) {
+  L_update_timer_cb = cb;
+}
+
+// [Static/Public] See if any external editors are open.
+//   App's timer cb can see if any editors need checking..
+//
+int ExternalCodeEditor::editors_open() {
+  return L_editors_open;
+}
+
+#endif /* !WIN32 */
+//
+// End of "$Id: ExternalCodeEditor_UNIX.cxx 10800 2016-07-10 00:00:00Z greg.ercolano $".
+//
Index: fluid/Fl_Type.cxx
===================================================================
--- fluid/Fl_Type.cxx	(revision 11804)
+++ fluid/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/fluid.cxx
===================================================================
--- fluid/fluid.cxx	(revision 11804)
+++ fluid/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...
@@ -185,6 +191,41 @@
   return name[which];
 }
 
+// Timer to watch for external editor modifications
+//    If one or more external editors open, check if their files were modified.
+//    If so: reload to ram, update size/mtime records, and change fluid's 'modified' state.
+//
+static void external_editor_timer(void*) {
+  int editors_open = ExternalCodeEditor::editors_open();
+  if ( G_debug ) printf("--- TIMER --- External editors open=%d\n", editors_open);
+  if ( editors_open > 0 ) {
+    // Walk tree looking for files modified by external editors.
+    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;
+        // Code changed by external editor?
+        if ( code->handle_editor_changes() ) {	// updates ram, file size/mtime
+          modified++;
+        }
+        if ( code->is_editing() ) {             // editor open?
+          code->reap_editor();                  // Try to reap; maybe it recently closed
+        }
+      }
+    }
+    if ( modified ) set_modflag(1);
+  }
+  // Repeat timeout if editors still open
+  //    The ExternalCodeEditor class handles start/stopping timer, we just
+  //    repeat_timeout() if it's already on. NOTE: above code may have reaped
+  //    only open editor, which would disable further timeouts. So *recheck*
+  //    if editors still open, to ensure we don't accidentally re-enable them.
+  //
+  if ( ExternalCodeEditor::editors_open() ) {
+    Fl::repeat_timeout(2.0, external_editor_timer);
+  }
+}
+
 void save_cb(Fl_Widget *, void *v) {
   Fl_Native_File_Chooser fnfc;
   const char *c = filename;
@@ -371,6 +412,10 @@
 }
 
 void exit_cb(Fl_Widget *,void *) {
+
+  // Stop any external editor update timers
+  ExternalCodeEditor::stop_update_timer();
+
   if (modflag)
     switch (fl_choice("Do you want to save changes to this user\n"
                       "interface before exiting?", "Cancel",
@@ -404,6 +449,12 @@
 
   undo_clear();
 
+  // Destroy tree
+  //    Doing so causes dtors to automatically close all external editors
+  //    and cleans up editor tmp files. Then remove fluid tmpdir /last/.
+  delete_all();
+  ExternalCodeEditor::tmpdir_clear();
+
   exit(0);
 }
 
@@ -1711,7 +1762,6 @@
 }
 #endif
 
-
 int main(int argc,char **argv) {
   int i = 1;
   
@@ -1800,6 +1850,9 @@
   signal(SIGINT,sigint);
 #endif
 
+  // Set (but do not start) timer callback 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
Index: fluid/ExternalCodeEditor_WIN32.cxx
===================================================================
--- fluid/ExternalCodeEditor_WIN32.cxx	(revision 0)
+++ fluid/ExternalCodeEditor_WIN32.cxx	(revision 0)
@@ -0,0 +1,555 @@
+//
+// "$Id: ExternalCodeEditor_WIN32.cxx 10800 2016-07-10 00:00:00Z greg.ercolano $".
+//
+//       External code editor management class for Windows
+//
+
+#ifdef WIN32	        /* This entire file windows only */
+
+#include <stdio.h>      // snprintf()
+
+#include <FL/Fl.H>      // Fl_Timeout_Handler..
+#include <FL/fl_ask.H>  // fl_alert()
+
+#include "ExternalCodeEditor_WIN32.h"
+
+extern int G_debug;     // defined in fluid.cxx
+
+// Static local data
+static int L_editors_open = 0;                          // keep track of #editors open
+static Fl_Timeout_Handler L_update_timer_cb = 0;        // app's update timer callback
+
+// [Static] Get error message string for last failed WIN32 function.
+// Returns a string pointing to static memory.
+//
+//     TODO: Is more code needed here to convert returned string to utf8? -erco
+//
+static const char *get_ms_errmsg() {
+  static char emsg[1024];
+  DWORD lastErr = GetLastError();
+  DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
+                FORMAT_MESSAGE_IGNORE_INSERTS  |
+                FORMAT_MESSAGE_FROM_SYSTEM;
+  LPSTR mbuf = 0;
+  DWORD size = FormatMessageA(flags, 0, lastErr, MAKELANGID(LANG_NEUTRAL,
+                              SUBLANG_DEFAULT), (LPSTR)&mbuf, 0, NULL);
+  if ( size == 0 ) {
+    _snprintf(emsg, sizeof(emsg), "Error Code %ld", long(lastErr));
+  } else {
+    // Copy mbuf -> emsg (with '\r's removed -- they screw up fl_alert())
+    for ( char *src=mbuf, *dst=emsg; 1; src++ ) {
+      if ( *src == '\0' ) { *dst = '\0'; break; }
+      if ( *src != '\r' ) { *dst++ = *src; }
+    }
+    LocalFree(mbuf);    // Free the buffer allocated by the system
+  }
+  return emsg;
+}
+
+// See if file exists
+static int is_file(const char *filename) {
+  DWORD att = GetFileAttributesA(filename);
+  if (att == INVALID_FILE_ATTRIBUTES) return 0;
+  if ( (att & FILE_ATTRIBUTE_DIRECTORY) == 0 ) return 1;        // not a dir == file
+  return 0;
+}
+
+// See if dir exists
+static int is_dir(const char *dirname) {
+  DWORD att = GetFileAttributesA(dirname);
+  if (att == INVALID_FILE_ATTRIBUTES) return 0;
+  if (att & FILE_ATTRIBUTE_DIRECTORY) return 1;
+  return 0;
+}
+
+// CTOR
+ExternalCodeEditor::ExternalCodeEditor() {
+  memset(&pinfo_, 0, sizeof(pinfo_));
+  memset(&file_mtime_, 0, sizeof(file_mtime_));
+  memset(&file_size_, 0, sizeof(file_size_));
+  filename_ = 0;
+}
+
+// DTOR
+ExternalCodeEditor::~ExternalCodeEditor() {
+  kill_editor();    // Kill any open editors, deletes tmp file
+  set_filename(0);  // free()s filename
+}
+
+// Set the filename. Handles memory allocation/free
+//     If set to NULL, frees memory.
+//
+void ExternalCodeEditor::set_filename(const char *val) {
+  if ( filename_ ) free((void*)filename_);
+  filename_ = val ? strdup(val) : 0;
+}
+
+// Is editor running?
+int ExternalCodeEditor::is_editing() {
+  return( (pinfo_.dwProcessId != 0) ? 1 : 0 );
+}
+
+// Terminate_app()'s callback to send WM_CLOSE to a single window. 
+static BOOL CALLBACK terminate_app_enum(HWND hwnd, LPARAM lParam) {
+  DWORD dwID;
+  GetWindowThreadProcessId(hwnd, &dwID);
+  if (dwID == (DWORD)lParam) {
+    PostMessage(hwnd, WM_CLOSE, 0, 0);
+    if ( G_debug )
+      printf("terminate_app_enum() sends WIN_CLOSE to hwnd=%p\n", (void*)hwnd);
+  }
+  return TRUE;
+}
+
+// Handle sending WIN_CLOSE to /all/ windows matching specified pid.
+// Wait up to msecTimeout for process to close, and if it doesn't, use TerminateProcess().
+//
+static int terminate_app(DWORD pid, DWORD msecTimeout) {
+  DWORD   dwRet ;
+  HANDLE hProc = OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE, FALSE, pid);
+  if ( !hProc ) return -1;
+  // terminate_app_enum() posts WM_CLOSE to all windows matching pid
+  EnumWindows((WNDENUMPROC)terminate_app_enum, (LPARAM) pid);
+  // Wait on handle. If it closes, great. If it times out, use TerminateProcess()
+  int ret = 0;
+  if ( WaitForSingleObject(hProc, msecTimeout) != WAIT_OBJECT_0 ) {
+    if ( G_debug ) {
+      printf("WARNING: sent WIN_CLOSE, but timeout after %ld msecs.."
+             "trying TerminateProcess\n", msecTimeout);
+    }
+    if ( TerminateProcess(hProc, 0) == 0 ) {
+      if ( G_debug ) {
+        printf("ERROR: TerminateProcess() for pid=%ld failed: %s\n",
+               long(pid), get_ms_errmsg());
+      }
+      ret = -1;
+    } else {
+      ret = 0;      // TerminateProcess succeeded
+    }
+  } else {
+    ret = 0;        // WaitForSingleObject() confirmed WIN_CLOSE succeeded
+  }
+  CloseHandle(hProc);
+  return ret;
+}
+
+// [Protected] Kill the running editor (if any) and cleanup
+//   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)pinfo_.dwProcessId);
+  if ( !is_editing() ) return;
+  switch ( terminate_app(pinfo_.dwProcessId, 500) ) {  // kill editor, wait up to 1/2 sec to die
+    case -1: { // error
+      fl_alert("Can't seem to close editor of file: %s\n"
+               "Please close editor and hit OK", filename());
+      break;
+    }
+    case 0: {  // success -- process reaped
+      DWORD pid = pinfo_.dwProcessId;     // save pid
+      reap_cleanup();
+      if ( G_debug )
+        printf("*** kill_editor() REAP pid=%ld #open=%ld\n",
+               long(pid), long(L_editors_open));
+      break;
+    }
+  }
+  return;
+}
+
+// Handle if file changed since last check, and update records if so.
+// Load new data into 'code', which caller must free().
+// If 'force' set, forces reload even if file size/time didn't change.
+//
+// Returns:
+//     0 -- file unchanged or not editing
+//     1 -- file changed, internal records updated, 'code' has new content
+//    -1 -- error getting file info (get_ms_errmsg() has reason)
+//
+// OPTIONAL TODO:
+//   Ignore changes made within the last 2 seconds,
+//   to give editor time to fully write out the file.
+//
+int ExternalCodeEditor::handle_changes(const char **code, int force) {
+  code[0] = 0;
+  if ( !is_editing() ) return 0;
+  // Sigh, have to open file to get file time/size :/
+  HANDLE fh = CreateFile(filename(),      // file to read
+                         GENERIC_READ,    // reading only
+                         FILE_SHARE_READ, // sharing -- allow read share; just getting file size
+                         NULL,            // security
+                         OPEN_EXISTING,   // create flags -- must exist
+                         0,               // misc flags
+                         NULL);           // templates
+  if ( fh == INVALID_HANDLE_VALUE ) return -1;
+  LARGE_INTEGER fsize;
+  // Get file size
+  if ( GetFileSizeEx(fh, &fsize) == 0 ) {
+    DWORD err = GetLastError();
+    CloseHandle(fh); 
+    SetLastError(err);  // return error from GetFileSizeEx(), not CloseHandle()
+    return -1;
+  }
+  // Get file time
+  FILETIME ftCreate, ftAccess, ftWrite;
+  if ( GetFileTime(fh, &ftCreate, &ftAccess, &ftWrite) == 0 ) {
+    DWORD err = GetLastError();
+    CloseHandle(fh); 
+    SetLastError(err);  // return error from GetFileTime(), not CloseHandle()
+    return -1;
+  }
+  // OK, now see if file changed; update records if so
+  int changed = 0;
+  if ( fsize.QuadPart != file_size_.QuadPart )
+    { changed = 1; file_size_ = fsize; }
+  if ( CompareFileTime(&ftWrite, &file_mtime_) != 0 )
+    { changed = 1; file_mtime_ = ftWrite; }
+  // Changes? Load file. Be sure to fallthru to CloseHandle()
+  int ret = 0;
+  if ( changed || force ) {
+    char *buf = (char*)malloc(fsize.QuadPart + 1);
+    DWORD count;
+    if ( ReadFile(fh, buf, fsize.QuadPart, &count, 0) == 0 ) {
+      fl_alert("ERROR: ReadFile() failed for %s: %s",
+               filename(), get_ms_errmsg());
+      free((void*)buf); buf = 0;
+      ret = -1;      // fallthru to CloseHandle()
+    } else if ( count != fsize.QuadPart ) {
+      fl_alert("ERROR: ReadFile() failed for %s:\n"
+               "expected %ld bytes, got %ld",
+               filename(), long(fsize.QuadPart), long(count));
+      free((void*)buf); buf = 0;
+      ret = -1;      // fallthru to CloseHandle()
+    } else {
+      // Successfully read changed file
+      buf[count] = '\0';
+      code[0] = buf; // return pointer to allocated buffer
+      ret = 1;       // fallthru to CloseHandle()
+    }
+  }
+  CloseHandle(fh);
+  return ret;
+}
+
+// Remove the tmp file (if it exists), and zero out filename/mtime/size
+// Returns:
+//    -1 -- on error (dialog is posted as to why)
+//     0 -- no file to remove
+//     1 -- file was removed
+//
+int ExternalCodeEditor::remove_tmpfile() {
+  const char *tmpfile = filename();
+  if ( G_debug ) printf("remove_tmpfile() '%s'\n", tmpfile ? tmpfile : "(empty)");
+  if ( !tmpfile ) return 0;
+  // Filename set? remove (if exists) and zero filename/mtime/size
+  if ( is_file(tmpfile) ) {
+    if ( G_debug ) printf("Removing tmpfile '%s'\n", tmpfile);
+    if ( DeleteFile(tmpfile) == 0 ) {
+      fl_alert("WARNING: Can't DeleteFile() '%s': %s", tmpfile, get_ms_errmsg());
+      return -1;
+    }
+  } else {
+    if ( G_debug ) printf("remove_tmpfile(): is_file(%s) failed\n", tmpfile);
+  }
+  set_filename(0);
+  memset(&file_mtime_, 0, sizeof(file_mtime_));
+  memset(&file_size_, 0, sizeof(file_size_));
+  return 1;
+}
+
+// [STATIC] Return tmpdir name for this fluid instance.
+//     Returns pointer to static memory.
+//
+const char* ExternalCodeEditor::tmpdir_name() {
+  char tempdir[100];
+  if (GetTempPath(sizeof(tempdir), tempdir) == 0 ) {
+    strcpy(tempdir, "c:\\windows\\temp");      // fallback
+  }
+  static char dirname[100];
+  _snprintf(dirname, sizeof(dirname), "%s.fluid-%ld",
+    tempdir, (long)GetCurrentProcessId());
+  if ( G_debug ) printf("tmpdir_name(): '%s'\n", dirname);
+  return dirname;
+}
+
+// [STATIC] Clear the external editor's tempdir
+//    Static so that the main program can call it on exit to clean up.
+//
+void ExternalCodeEditor::tmpdir_clear() {
+  const char *tmpdir = tmpdir_name();
+  if ( is_dir(tmpdir) ) {
+    if ( G_debug ) printf("Removing tmpdir '%s'\n", tmpdir);
+    if ( RemoveDirectory(tmpdir) == 0 ) {
+      fl_alert("WARNING: Can't RemoveDirectory() '%s': %s",
+               tmpdir, get_ms_errmsg());
+    }
+  }
+}
+
+// Creates temp dir (if doesn't exist) and returns the dirname
+// as a static string. Returns NULL on error, dialog shows reason.
+//
+const char* ExternalCodeEditor::create_tmpdir() {
+  const char *dirname = tmpdir_name();
+  if ( ! is_dir(dirname) ) {
+    if ( CreateDirectory(dirname,0) == 0 ) {
+      fl_alert("can't create directory '%s': %s",
+        dirname, get_ms_errmsg());
+      return NULL;
+    }
+  }
+  return dirname;
+}
+
+// Returns temp filename in static buffer.
+//    Returns NULL if can't, posts dialog explaining why.
+//
+const char* ExternalCodeEditor::tmp_filename() {
+  static char path[512];
+  const char *tmpdir = create_tmpdir();
+  if ( !tmpdir ) return 0;
+  extern const char *code_file_name;    // fluid's global
+  const char *ext  = code_file_name;    // e.g. ".cxx"
+  _snprintf(path, sizeof(path), "%s\\%p%s", tmpdir, (void*)this, ext);
+  path[sizeof(path)-1] = 0;
+  return path;
+}
+
+// Save string 'code' to 'filename', returning file's mtime/size
+// 'code' can be NULL -- writes an empty file if so.
+// Returns:
+//    0 on success
+//   -1 on error (posts dialog with reason)
+//
+static int save_file(const char *filename,
+                     const char *code,
+                     FILETIME &file_mtime,        // return these since in win32 it's..
+                     LARGE_INTEGER &file_size) {  // ..efficient to get while file open
+  if ( code == 0 ) code = "";   // NULL? write an empty file
+  memset(&file_mtime, 0, sizeof(file_mtime));
+  memset(&file_size, 0, sizeof(file_size));
+  HANDLE fh = CreateFile(filename,                // filename
+                         GENERIC_WRITE,           // write only
+                         0,                       // sharing -- no share during write
+                         NULL,                    // security
+                         CREATE_ALWAYS,           // create flags -- recreate
+                         FILE_ATTRIBUTE_NORMAL,   // misc flags
+                         NULL);                   // templates
+  if ( fh == INVALID_HANDLE_VALUE ) {
+    fl_alert("ERROR: couldn't create file '%s': %s",
+             filename, get_ms_errmsg());
+    return(-1);
+  }
+  // Write the file, being careful to CloseHandle() even on errs
+  DWORD clen = strlen(code);
+  DWORD count = 0;
+  int ret = 0;
+  if ( WriteFile(fh, code, clen, &count, NULL) == 0 ) {
+    fl_alert("ERROR: WriteFile() '%s': %s", filename, get_ms_errmsg());
+    ret = -1; // fallthru to CloseHandle()
+  } else if ( count != clen ) {
+    fl_alert("ERROR: WriteFile() '%s': wrote only %lu bytes, expected %lu",
+             filename, (unsigned long)count, (unsigned long)clen);
+    ret = -1; // fallthru to CloseHandle()
+  }
+  // Get mtime/size before closing
+  {
+    FILETIME ftCreate, ftAccess, ftWrite;
+    if ( GetFileSizeEx(fh, &file_size) == 0 ) {
+      fl_alert("ERROR: save_file(%s): GetFileSizeEx() failed: %s\n",
+               filename, get_ms_errmsg());
+    }
+    if ( GetFileTime(fh, &ftCreate, &ftAccess, &ftWrite) == 0 ) {
+      fl_alert("ERROR: save_file(%s): GetFileTime() failed: %s\n",
+               filename, get_ms_errmsg());
+    }
+    file_mtime = ftWrite;
+  }
+  // Close, done
+  CloseHandle(fh);
+  return(ret);
+}
+
+// Start editor
+// Returns:
+//    >  0 on success, leaves editor child process running
+//    > -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);
+  // CREATE FLAGS
+  int createflags = 0;
+
+  // STARTUP INFO
+  STARTUPINFO sinfo;
+  memset(&sinfo, 0, sizeof(sinfo));
+  sinfo.cb          = sizeof(sinfo);
+  sinfo.dwFlags     = 0;
+  sinfo.wShowWindow = 0;
+
+  // PROCESS INFO
+  memset(&pinfo_, 0, sizeof(pinfo_));
+
+  // COMMAND
+  char cmd[1024];
+  _snprintf(cmd, sizeof(cmd), "%s %s", editor_cmd, filename);
+
+  // START EDITOR
+  if (CreateProcess(NULL,               // app name
+                    (char*)cmd,         // command to exec
+                    NULL,               // secure attribs
+                    NULL,               // thread secure attribs
+                    NULL,               // handle inheritence
+                    NULL,               // creation flags
+                    NULL,               // environ block
+                    NULL,               // current dir
+                    &sinfo,             // startup info
+                    &pinfo_) == 0 ) {   // process info
+    fl_alert("CreateProcess() failed to start '%s': %s",
+             cmd, get_ms_errmsg());
+    return(-1);
+  }
+  if ( L_editors_open++ == 0 )  // first editor? start timers
+    { start_update_timer(); }
+  if ( G_debug )
+    printf("--- EDITOR STARTED: pid_=%ld #open=%d\n",
+           (long)pinfo_.dwProcessId, L_editors_open);
+  return 0;
+}
+
+// [Protected] Cleanup after editor reaped:
+//    > Remove tmpfile, zeroes mtime/size/filename
+//    > Close process handles
+//    > Zero out process info
+//    > Decrease editor count
+//
+void ExternalCodeEditor::reap_cleanup() {
+  remove_tmpfile();                    // also zeroes mtime/size/filename
+  CloseHandle(pinfo_.hProcess);        // close process handle
+  CloseHandle(pinfo_.hThread);         // close thread handle
+  memset(&pinfo_, 0, sizeof(pinfo_));  // clear pinfo_
+  if ( --L_editors_open <= 0 )
+    { stop_update_timer(); }
+}
+
+// Try to reap external editor process
+// Returns:
+//   -2 -- editor not open
+//   -1 -- WaitForSingleObject() failed (get_ms_errmsg() has reason)
+//    0 -- process still running
+//   >0 -- process finished + reaped (value is pid)
+//         Handles removing tmpfile/zeroing file_mtime/file_size/filename
+//
+DWORD ExternalCodeEditor::reap_editor() {
+  if ( pinfo_.dwProcessId == 0 ) return -2;
+  int err;
+  DWORD msecs_wait = 50;   // .05 sec
+  switch ( err = WaitForSingleObject(pinfo_.hProcess, msecs_wait) ) {
+    case WAIT_TIMEOUT: {   // process didn't reap, still running
+      return 0;
+    }
+    case WAIT_OBJECT_0: {  // reaped
+      DWORD pid = pinfo_.dwProcessId;      // save pid
+      reap_cleanup();
+      if ( G_debug ) printf("*** EDITOR REAPED: pid=%ld #open=%d\n",
+                            long(pid), L_editors_open);
+      return pid;
+    }
+    case WAIT_FAILED: {    // failed
+      return -1;
+    }
+  }
+  return -1;               // any other return unexpected
+}
+
+// [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) {
+  // Make sure a temp filename exists
+  if ( !filename() ) {
+    set_filename(tmp_filename());
+    if ( !filename() ) return -1;
+  }
+  // See if tmpfile already exists or editor already open
+  if ( is_file(filename()) ) {
+    if ( is_editing() ) {
+      // See if editor recently closed but not reaped; try to reap
+      DWORD wpid = reap_editor();
+      switch (wpid) {
+        case -1:        // wait failed
+          fl_alert("ERROR: WaitForSingleObject() failed: %s\nfile='%s', pid=%ld",
+            get_ms_errmsg(), filename(), long(pinfo_.dwProcessId));
+          return -1;
+        case 0:         // process still running
+          fl_alert("Editor Already Open\n  file='%s'\n  pid=%ld",
+            filename(), long(pinfo_.dwProcessId));
+          return 0;
+        default:        // process reaped, wpid is pid reaped
+          if ( G_debug )
+            printf("*** REAPED EXTERNAL EDITOR: PID %ld\n", long(wpid));
+          break;        // fall thru to open new editor instance
+      }
+      // Reinstate tmp filename (reap_editor() clears it)
+      set_filename(tmp_filename());
+    }
+  }
+  // Save code to tmpfile, getting mtime/size
+  if ( save_file(filename(), code, file_mtime_, file_size_) < 0 ) {
+    return -1;  // errors were shown in dialog
+  }
+  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
+  }
+  // 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/Static] Start update timer
+void ExternalCodeEditor::start_update_timer() {
+  if ( !L_update_timer_cb ) return;
+  if ( G_debug ) printf("--- TIMER: STARTING UPDATES\n");
+  Fl::add_timeout(2.0, L_update_timer_cb);
+}
+
+// [Public/Static] Stop update timer
+void ExternalCodeEditor::stop_update_timer() {
+  if ( !L_update_timer_cb ) return;
+  if ( G_debug ) printf("--- TIMER: STOPPING UPDATES\n");
+  Fl::remove_timeout(L_update_timer_cb);
+}
+
+// [Public/Static] Set app's external editor update timer callback
+//   This is the app's callback callback we start while editors are open,
+//   and stop when all editors are closed.
+//
+void ExternalCodeEditor::set_update_timer_callback(Fl_Timeout_Handler cb) {
+  L_update_timer_cb = cb;
+}
+
+// [Static/Public] See if any external editors are open.
+//   App's timer cb can see if any editors need checking..
+//
+int ExternalCodeEditor::editors_open() {
+  return L_editors_open;
+}
+
+#endif /* WIN32 */
+//
+// End of "$Id: ExternalCodeEditor_WIN32.cxx 10800 2016-07-10 00:00:00Z greg.ercolano $".
+//
Index: fluid/ExternalCodeEditor_UNIX.h
===================================================================
--- fluid/ExternalCodeEditor_UNIX.h	(revision 0)
+++ fluid/ExternalCodeEditor_UNIX.h	(revision 0)
@@ -0,0 +1,51 @@
+//
+// "$Id: ExternalCodeEditor_UNIX.h 10800 2016-07-10 00:00:00Z greg.ercolano $".
+//
+//       External code editor management class for Unix
+//
+//       Handles starting and keeping track of an external text editor,
+//       including process start, temp file creation/removal, bookkeeping, killing..
+//
+#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>
+
+class ExternalCodeEditor {
+  int pid_;
+  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)
+  const char *filename_;
+protected:
+  void kill_editor();
+  const char *create_tmpdir();
+  const char *tmp_filename();
+  int start_editor(const char *cmd, const char *filename);
+  void set_filename(const char *val);
+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 handle_changes(const char **code, int force=0);
+  int remove_tmpfile();
+  // Public static methods
+  static void start_update_timer();
+  static void stop_update_timer();
+  static const char* tmpdir_name();
+  static void tmpdir_clear();
+  static int editors_open();
+  static void set_update_timer_callback(Fl_Timeout_Handler);
+};
+
+#endif /*_EXTCODEEDITOR_H */
+//
+// End of "$Id: ExternalCodeEditor_UNIX.h 10800 2016-07-10 00:00:00Z greg.ercolano $".
+//
Index: ide/VisualC2010/fluid.vcxproj
===================================================================
--- ide/VisualC2010/fluid.vcxproj	(revision 11804)
+++ ide/VisualC2010/fluid.vcxproj	(working copy)
@@ -26,18 +26,22 @@
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Cairo|Win32'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug Cairo|Win32'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
     <ConfigurationType>Application</ConfigurationType>
     <UseOfMfc>false</UseOfMfc>
+    <PlatformToolset>v110</PlatformToolset>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -317,6 +321,7 @@
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
       <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
+    <ClCompile Include="..\..\fluid\ExternalCodeEditor_WIN32.cxx" />
     <ClCompile Include="..\..\fluid\factory.cxx">
       <Optimization Condition="'$(Configuration)|$(Platform)'=='Debug Cairo|Win32'">Disabled</Optimization>
       <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug Cairo|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
@@ -527,7 +532,10 @@
       <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
     </ProjectReference>
   </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\fluid\ExternalCodeEditor_WIN32.h" />
+  </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
\ No newline at end of file
+</Project>
