synchronized public void message(String s) {
    //System.out.println("M" + s.length() + ":" + s.trim()); // + "MMM" + s.length());

    // this eats the CRLFs on the lines.. oops.. do it later
    //if (s.trim().length() == 0) return;

    // this is PApplet sending a message (via System.out.println)
    // that signals that the applet has been quit.
    if (s.indexOf(PApplet.EXTERNAL_STOP) == 0) {
      //System.out.println("external: quit");
      editor.internalCloseRunner();
      return;
    }

    // this is the PApplet sending us a message that the applet
    // is being moved to a new window location
    if (s.indexOf(PApplet.EXTERNAL_MOVE) == 0) {
      String nums = s.substring(s.indexOf(' ') + 1).trim();
      int space = nums.indexOf(' ');
      int left = Integer.parseInt(nums.substring(0, space));
      int top = Integer.parseInt(nums.substring(space + 1));
      editor.setSketchLocation(new Point(left, top));
      //System.out.println("external: move to " + left + " " + top);
      return;
    }

    // Removed while doing cleaning for 0145,
    // it seems that this is never actually printed out.
    /*
    // this is PApplet sending a message saying "i'm about to spew
    // a stack trace because an error occurred during PApplet.run()"
    if (s.indexOf(PApplet.LEECH_WAKEUP) == 0) {
      // newMessage being set to 'true' means that the next time
      // message() is called, expect the first line of the actual
      // error message & stack trace to be sent from the applet.
      newMessage = true;
      return;  // this line ignored
    }
    */

    // these are used for debugging, in case there are concerns
    // that some errors aren't coming through properly
    /*
    if (s.length() > 2) {
      System.err.println(newMessage);
      System.err.println("message " + s.length() + ":" + s);
    }
    */
    // always shove out the mesage, since it might not fall under
    // the same setup as we're expecting
    System.err.print(s);
    //System.err.println("[" + s.length() + "] " + s);
    System.err.flush();

//    // exit here because otherwise the exception name
//    // may be titled with a blank string
//    if (s.trim().length() == 0) return;
//
//    // annoying, because it seems as though the terminators
//    // aren't being sent properly
//    //System.err.println(s);
//
//    //if (newMessage && s.length() > 2) {
//    if (newMessage) {
//      exception = new RunnerException(s);  // type of java ex
//      exception.hideStackTrace();
//      //System.out.println("setting ex type to " + s);
//      newMessage = false;
//      foundMessageSource = false;
//      messageLineCount = 0;
//
//    } else {
//      messageLineCount++;
//
//      /*
//java.lang.NullPointerException
//        at javatest.<init>(javatest.java:5)
//        at Temporary_2425_1153.draw(Temporary_2425_1153.java:11)
//        at PApplet.nextFrame(PApplet.java:481)
//        at PApplet.run(PApplet.java:428)
//        at java.lang.Thread.run(Unknown Source)
//      */
//
//      if (!foundMessageSource) {
//        //    "     at javatest.<init>(javatest.java:5)"
//        // -> "javatest.<init>(javatest.java:5)"
//        int atIndex = s.indexOf("at ");
//        if (atIndex == -1) {
//          //System.err.println(s);  // stop double-printing exceptions
//          return;
//        }
//        s = s.substring(atIndex + 3);
//
//        // added for 0124 to improve error handling
//        // not highlighting lines if it's in the p5 code
//        if (s.startsWith("processing.")) return;
//        // no highlight if it's java.lang.whatever
//        if (s.startsWith("java.")) return;
//
//        //    "javatest.<init>(javatest.java:5)"
//        // -> "javatest.<init>" and "(javatest.java:5)"
//        int startParen = s.indexOf('(');
//        // at javatest.<init>(javatest.java:5)
//        //String pkgClassFxn = null;
//        //String fileLine = null;
//        int codeIndex = -1;
//        int lineNumber = -1;
//
//        if (startParen == -1) {
//          //pkgClassFxn = s;
//
//        } else {
//          //pkgClassFxn = s.substring(0, startParen);
//
//          // "(javatest.java:5)"
//          String fileAndLine = s.substring(startParen + 1);
//          int stopParen = fileAndLine.indexOf(')');
//          //fileAndLine = fileAndLine.substring(0, fileAndLine.length() - 1);
//          fileAndLine = fileAndLine.substring(0, stopParen);
//          //System.out.println("file 'n line " + fileAndLine);
//
//          //if (!fileAndLine.equals("Unknown Source")) {
//          // "javatest.java:5"
//          int colonIndex = fileAndLine.indexOf(':');
//          if (colonIndex != -1) {
//            String filename = fileAndLine.substring(0, colonIndex);
//            // "javatest.java" and "5"
//            //System.out.println("filename = " + filename);
//            //System.out.println("pre0 = " + sketch.code[0].preprocName);
//            //for (int i = 0; i < sketch.codeCount; i++) {
//            //System.out.println(i + " " + sketch.code[i].lineOffset + " " +
//            //                   sketch.code[i].preprocName);
//            //}
//            lineNumber =
//              Integer.parseInt(fileAndLine.substring(colonIndex + 1)) - 1;
//
//            for (int i = 0; i < sketch.getCodeCount(); i++) {
//              SketchCode code = sketch.getCode(i);
//              //System.out.println(code.preprocName + " " + lineNumber + " " +
//              //                 code.preprocOffset);
//              if (((code.preprocName == null) &&
//                   (lineNumber >= code.preprocOffset)) ||
//                  ((code.preprocName != null) &&
//                   code.preprocName.equals(filename))) {
//                codeIndex = i;
//                //System.out.println("got codeindex " + codeIndex);
//                //break;
//                //} else if (
//              }
//            }
//
//            if (codeIndex != -1) {
//              //System.out.println("got line num " + lineNumber);
//              // in case this was a tab that got embedded into the main .java
//              lineNumber -= sketch.getCode(codeIndex).preprocOffset;
//
//              // this may have a paren on the end, if so need to strip
//              // down to just the digits
//              /*
//              int lastNumberIndex = colonIndex + 1;
//              while ((lastNumberIndex < fileAndLine.length()) &&
//                     Character.isDigit(fileAndLine.charAt(lastNumberIndex))) {
//                lastNumberIndex++;
//              }
//              */
//
//              // lineNumber is 1-indexed, but editor wants zero-indexed
//              // getMessage() will be what's shown in the editor
//              exception =
//                new RunnerException(exception.getMessage(),
//                                    codeIndex, lineNumber, -1);
//              exception.hideStackTrace();
//              foundMessageSource = true;
//            }
//          }
//        }
//        editor.error(exception);
//
//      /*
//      int index = s.indexOf(className + ".java");
//      if (index != -1) {
//        int len = (className + ".java").length();
//        String lineNumberStr = s.substring(index + len + 1);
//        index = lineNumberStr.indexOf(')');
//        lineNumberStr = lineNumberStr.substring(0, index);
//        try {
//          exception.line = Integer.parseInt(lineNumberStr) - 1; //2;
//        } catch (NumberFormatException e) { }
//          //e.printStackTrace();  // a recursive error waiting to happen?
//        // if nfe occurs, who cares, still send the error on up
//        editor.error(exception);
//      */
//
//        /*
//          // WARNING THESE ARE DISABLED!!
//      } else if ((index = s.indexOf(className + ".class")) != -1) {
//        // code to check for:
//        // at Temporary_484_3845.loop(Compiled Code)
//        // would also probably get:
//        // at Temporary_484_3845.loop
//        // which (i believe) is used by the mac and/or jview
//        String functionStr = s.substring(index +
//                                         (className + ".class").length() + 1);
//        index = functionStr.indexOf('(');
//        if (index != -1) {
//          functionStr = functionStr.substring(0, index);
//        }
//        exception = new RunnerException(//"inside \"" + functionStr + "()\": " +
//                                     exception.getMessage() +
//                                     " inside " + functionStr + "() " +
//                                     "[add Compiler.disable() to setup()]");
//        editor.error(exception);
//        // this will fall through in tihs example:
//        // at Temporary_4636_9696.pootie(Compiled Code)
//        // at Temporary_4636_9696.loop(Temporary_4636_9696.java:24)
//        // because pootie() (re)sets the exception title
//        // and throws it, but then the line number gets set
//        // because of the line that comes after
//        */
//
//      } else if (messageLineCount > 10) {  // 5 -> 10 for 0088
//        // this means the class name may not be mentioned
//        // in the stack trace.. this is just a general purpose
//        // error, but needs to make it through anyway.
//        // so if five lines have gone past, might as well signal
//        messageLineCount = -100;
//        exception = new RunnerException(exception.getMessage());
//        exception.hideStackTrace();
//        editor.error(exception);
//
//      } else {
//        //System.err.print(s);
//      }