/** Handler for the Rename Code menu option. */
  public void handleRenameCode() {
    SketchFile current = editor.getCurrentTab().getSketchFile();

    editor.status.clearState();
    // make sure the user didn't hide the sketch folder
    ensureExistence();

    if (current.isPrimary() && editor.untitled) {
      Base.showMessage(
          tr("Sketch is Untitled"),
          tr("How about saving the sketch first \n" + "before trying to rename it?"));
      return;
    }

    // if read-only, give an error
    if (isReadOnly(
        BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath())) {
      // if the files are read-only, need to first do a "save as".
      Base.showMessage(
          tr("Sketch is Read-Only"),
          tr(
              "Some files are marked \"read-only\", so you'll\n"
                  + "need to re-save the sketch in another location,\n"
                  + "and try again."));
      return;
    }

    // ask for new name of file (internal to window)
    // TODO maybe just popup a text area?
    renamingCode = true;
    String prompt = current.isPrimary() ? "New name for sketch:" : "New name for file:";
    String oldName = current.getPrettyName();
    editor.status.edit(prompt, oldName);
  }
  /** Save all code in the current sketch. */
  public boolean save() throws IOException {
    // make sure the user didn't hide the sketch folder
    ensureExistence();

    if (isReadOnly(
        BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath())) {
      Base.showMessage(
          tr("Sketch is read-only"),
          tr(
              "Some files are marked \"read-only\", so you'll\n"
                  + "need to re-save this sketch to another location."));
      return saveAs();
    }

    // rename .pde files to .ino
    List<SketchFile> oldFiles = new ArrayList<>();
    for (SketchFile file : sketch.getFiles()) {
      if (file.isExtension(Sketch.OLD_SKETCH_EXTENSIONS)) oldFiles.add(file);
    }

    if (oldFiles.size() > 0) {
      if (PreferencesData.get("editor.update_extension") == null) {
        Object[] options = {tr("OK"), tr("Cancel")};
        int result =
            JOptionPane.showOptionDialog(
                editor,
                tr(
                    "In Arduino 1.0, the default file extension has changed\n"
                        + "from .pde to .ino.  New sketches (including those created\n"
                        + "by \"Save-As\") will use the new extension.  The extension\n"
                        + "of existing sketches will be updated on save, but you can\n"
                        + "disable this in the Preferences dialog.\n"
                        + "\n"
                        + "Save sketch and update its extension?"),
                tr(".pde -> .ino"),
                JOptionPane.OK_CANCEL_OPTION,
                JOptionPane.QUESTION_MESSAGE,
                null,
                options,
                options[0]);

        if (result != JOptionPane.OK_OPTION) return false; // save cancelled

        PreferencesData.setBoolean("editor.update_extension", true);
      }

      if (PreferencesData.getBoolean("editor.update_extension")) {
        // Do rename of all .pde files to new .ino extension
        for (SketchFile file : oldFiles) {
          File newName =
              FileUtils.replaceExtension(file.getFile(), Sketch.DEFAULT_SKETCH_EXTENSION);
          file.renameTo(newName.getName());
        }
      }
    }

    sketch.save();
    return true;
  }
  /** Remove a piece of code from the sketch and from the disk. */
  public void handleDeleteCode() throws IOException {
    SketchFile current = editor.getCurrentTab().getSketchFile();
    editor.status.clearState();
    // make sure the user didn't hide the sketch folder
    ensureExistence();

    // if read-only, give an error
    if (isReadOnly(
        BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath())) {
      // if the files are read-only, need to first do a "save as".
      Base.showMessage(
          tr("Sketch is Read-Only"),
          tr(
              "Some files are marked \"read-only\", so you'll\n"
                  + "need to re-save the sketch in another location,\n"
                  + "and try again."));
      return;
    }

    // confirm deletion with user, yes/no
    Object[] options = {tr("OK"), tr("Cancel")};
    String prompt =
        current.isPrimary()
            ? tr("Are you sure you want to delete this sketch?")
            : I18n.format(tr("Are you sure you want to delete \"{0}\"?"), current.getPrettyName());
    int result =
        JOptionPane.showOptionDialog(
            editor,
            prompt,
            tr("Delete"),
            JOptionPane.YES_NO_OPTION,
            JOptionPane.QUESTION_MESSAGE,
            null,
            options,
            options[0]);
    if (result == JOptionPane.YES_OPTION) {
      if (current.isPrimary()) {
        sketch.delete();
        editor.base.handleClose(editor);
      } else {
        // delete the file
        if (!current.delete(sketch.getBuildPath().toPath())) {
          Base.showMessage(
              tr("Couldn't do it"),
              I18n.format(tr("Could not delete \"{0}\"."), current.getFileName()));
          return;
        }

        // just set current tab to the main tab
        editor.selectTab(0);

        // update the tabs
        editor.header.repaint();
      }
    }
  }
  /** Convert to sanitized name and alert the user if changes were made. */
  private static String checkName(String origName) {
    String newName = BaseNoGui.sanitizeName(origName);

    if (!newName.equals(origName)) {
      String msg =
          tr(
              "The sketch name had to be modified. Sketch names can only consist\n"
                  + "of ASCII characters and numbers (but cannot start with a number).\n"
                  + "They should also be less than 64 characters long.");
      System.out.println(msg);
    }
    return newName;
  }
  /**
   * Prompt the user for a new file to the sketch, then call the other addFile() function to
   * actually add it.
   */
  public void handleAddFile() {
    // make sure the user didn't hide the sketch folder
    ensureExistence();

    // if read-only, give an error
    if (isReadOnly(
        BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath())) {
      // if the files are read-only, need to first do a "save as".
      Base.showMessage(
          tr("Sketch is Read-Only"),
          tr(
              "Some files are marked \"read-only\", so you'll\n"
                  + "need to re-save the sketch in another location,\n"
                  + "and try again."));
      return;
    }

    // get a dialog, select a file to add to the sketch
    FileDialog fd =
        new FileDialog(
            editor,
            tr("Select an image or other data file to copy to your sketch"),
            FileDialog.LOAD);
    fd.setVisible(true);

    String directory = fd.getDirectory();
    String filename = fd.getFile();
    if (filename == null) return;

    // copy the file into the folder. if people would rather
    // it move instead of copy, they can do it by hand
    File sourceFile = new File(directory, filename);

    // now do the work of adding the file
    boolean result = addFile(sourceFile);

    if (result) {
      editor.statusNotice(tr("One file added to the sketch."));
      PreferencesData.set("last.folder", sourceFile.getAbsolutePath());
    }
  }
  /** Handler for the New Code menu option. */
  public void handleNewCode() {
    editor.status.clearState();
    // make sure the user didn't hide the sketch folder
    ensureExistence();

    // if read-only, give an error
    if (isReadOnly(
        BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath())) {
      // if the files are read-only, need to first do a "save as".
      Base.showMessage(
          tr("Sketch is Read-Only"),
          tr(
              "Some files are marked \"read-only\", so you'll\n"
                  + "need to re-save the sketch in another location,\n"
                  + "and try again."));
      return;
    }

    renamingCode = false;
    editor.status.edit(tr("Name for new file:"), "");
  }
  /**
   * Handles 'Save As' for a sketch.
   *
   * <p>This basically just duplicates the current sketch folder to a new location, and then calls
   * 'Save'. (needs to take the current state of the open files and save them to the new folder..
   * but not save over the old versions for the old sketch..)
   *
   * <p>Also removes the previously-generated .class and .jar files, because they can cause trouble.
   */
  protected boolean saveAs() throws IOException {
    // get new name for folder
    FileDialog fd = new FileDialog(editor, tr("Save sketch folder as..."), FileDialog.SAVE);
    if (isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath())
        || isUntitled()) {
      // default to the sketchbook folder
      fd.setDirectory(BaseNoGui.getSketchbookFolder().getAbsolutePath());
    } else {
      // default to the parent folder of where this was
      // on macs a .getParentFile() method is required

      fd.setDirectory(sketch.getFolder().getParentFile().getAbsolutePath());
    }
    String oldName = sketch.getName();
    fd.setFile(oldName);

    fd.setVisible(true);
    String newParentDir = fd.getDirectory();
    String newName = fd.getFile();

    // user canceled selection
    if (newName == null) return false;
    newName = SketchController.checkName(newName);

    File newFolder = new File(newParentDir, newName);

    // check if the paths are identical
    if (newFolder.equals(sketch.getFolder())) {
      // just use "save" here instead, because the user will have received a
      // message (from the operating system) about "do you want to replace?"
      return save();
    }

    // check to see if the user is trying to save this sketch inside itself
    try {
      String newPath = newFolder.getCanonicalPath() + File.separator;
      String oldPath = sketch.getFolder().getCanonicalPath() + File.separator;

      if (newPath.indexOf(oldPath) == 0) {
        Base.showWarning(
            tr("How very Borges of you"),
            tr(
                "You cannot save the sketch into a folder\n"
                    + "inside itself. This would go on forever."),
            null);
        return false;
      }
    } catch (IOException e) {
      // ignore
    }

    // if the new folder already exists, then need to remove
    // its contents before copying everything over
    // (user will have already been warned)
    if (newFolder.exists()) {
      FileUtils.recursiveDelete(newFolder);
    }
    // in fact, you can't do this on windows because the file dialog
    // will instead put you inside the folder, but it happens on osx a lot.

    try {
      sketch.saveAs(newFolder);
    } catch (IOException e) {
      // This does not pass on e, to prevent showing a backtrace for "normal"
      // errors.
      Base.showWarning(tr("Error"), e.getMessage(), null);
    }
    // Name changed, rebuild the sketch menus
    // editor.sketchbook.rebuildMenusAsync();
    editor.base.rebuildSketchbookMenus();
    editor.header.rebuild();

    // Make sure that it's not an untitled sketch
    setUntitled(false);

    // let Editor know that the save was successful
    return true;
  }
  /**
   * This is called upon return from entering a new file name. (that is, from either newCode or
   * renameCode after the prompt) This code is almost identical for both the newCode and renameCode
   * cases, so they're kept merged except for right in the middle where they diverge.
   */
  protected void nameCode(String newName) {
    // make sure the user didn't hide the sketch folder
    ensureExistence();

    newName = newName.trim();
    if (newName.equals("")) return;

    if (newName.charAt(0) == '.') {
      Base.showWarning(tr("Problem with rename"), tr("The name cannot start with a period."), null);
      return;
    }

    FileUtils.SplitFile split = FileUtils.splitFilename(newName);
    if (split.extension.equals("")) split.extension = Sketch.DEFAULT_SKETCH_EXTENSION;

    if (!Sketch.EXTENSIONS.contains(split.extension)) {
      String msg = I18n.format(tr("\".{0}\" is not a valid extension."), split.extension);
      Base.showWarning(tr("Problem with rename"), msg, null);
      return;
    }

    // Sanitize name
    split.basename = BaseNoGui.sanitizeName(split.basename);
    newName = split.join();

    if (renamingCode) {
      SketchFile current = editor.getCurrentTab().getSketchFile();

      if (current.isPrimary()) {
        if (!split.extension.equals(Sketch.DEFAULT_SKETCH_EXTENSION)) {
          Base.showWarning(
              tr("Problem with rename"), tr("The main file cannot use an extension"), null);
          return;
        }

        // Primary file, rename the entire sketch
        final File parent = sketch.getFolder().getParentFile();
        File newFolder = new File(parent, split.basename);
        try {
          sketch.renameTo(newFolder);
        } catch (IOException e) {
          // This does not pass on e, to prevent showing a backtrace for
          // "normal" errors.
          Base.showWarning(tr("Error"), e.getMessage(), null);
          return;
        }

        editor.base.rebuildSketchbookMenus();
      } else {
        // Non-primary file, rename just that file
        try {
          current.renameTo(newName);
        } catch (IOException e) {
          // This does not pass on e, to prevent showing a backtrace for
          // "normal" errors.
          Base.showWarning(tr("Error"), e.getMessage(), null);
          return;
        }
      }

    } else { // creating a new file
      SketchFile file;
      try {
        file = sketch.addFile(newName);
        editor.addTab(file, "");
      } catch (IOException e) {
        // This does not pass on e, to prevent showing a backtrace for
        // "normal" errors.
        Base.showWarning(tr("Error"), e.getMessage(), null);
        return;
      }
      editor.selectTab(editor.findTabIndex(file));
    }

    // update the tabs
    editor.header.rebuild();
  }