@Override
  public boolean execute(Plugin plugin, CommandSender sender, String[] args) {

    String menuName = args[0];
    String itemLabel = SMSUtil.unEscape(args[1]);

    if (itemLabel.matches("@[0-9]+")) {
      // backwards compatibility - numeric indices should be prefixed with a '@'
      // but we'll allow raw numbers to be used
      itemLabel = itemLabel.substring(1);
    }

    try {
      SMSMenu menu = getMenu(sender, menuName);
      menu.ensureAllowedToModify(sender);
      int pos = menu.indexOfItem(itemLabel);
      SMSMenuItem menuItem = menu.getItemAt(pos);
      menu.removeItem(pos);
      menu.notifyObservers(new RemoveItemAction(sender, menuItem));
      MiscUtil.statusMessage(
          sender, "Menu entry &f#" + itemLabel + "&- removed from &e" + menu.getName());
    } catch (IndexOutOfBoundsException e) {
      throw new SMSException("Item index " + itemLabel + " out of range");
    } catch (IllegalArgumentException e) {
      throw new SMSException(e.getMessage());
    }

    return true;
  }
  @Override
  public boolean execute(ScrollingMenuSign plugin, Player player, String[] args)
      throws SMSException {
    SMSView view = null;
    SMSMenu menu = plugin.getHandler().getMenu(args[0]);

    if (args.length == 2 && args[1].equalsIgnoreCase("-spout")) { // spout view
      if (plugin.isSpoutEnabled()) view = SMSSpoutView.addSpoutViewToMenu(menu);
      else throw new SMSException("Server is not Spout-enabled");
    } else if (args.length == 3 && args[1].equalsIgnoreCase("-sign")) { // sign view
      Location loc = MiscUtil.parseLocation(args[2], player);
      view = SMSSignView.addSignToMenu(menu, loc);
    } else if (args.length == 3 && args[1].equalsIgnoreCase("-redstone")) { // redstone view
      Location loc = MiscUtil.parseLocation(args[2], player);
      view = SMSRedstoneView.addRedstoneViewToMenu(menu, loc);
    } else if (args.length == 2
        && (args[1].equalsIgnoreCase("-sign") || args[1].equalsIgnoreCase("-redstone"))) {
      // create a new view interactively
      notFromConsole(player);
      String type = args[1].substring(1);
      MiscUtil.statusMessage(
          player,
          "Left-click a block to add it as a &9"
              + type
              + "&- view on menu &e"
              + menu.getName()
              + "&-.");
      MiscUtil.statusMessage(player, "Right-click anywhere to cancel.");
      plugin.expecter.expectingResponse(player, new ExpectViewCreation(menu, args[1]));
      return true;
    } else if (args.length == 3 && args[1].equalsIgnoreCase("-map")) { // map view
      try {
        short mapId = Short.parseShort(args[2]);
        view = SMSMapView.addMapToMenu(menu, mapId);
      } catch (NumberFormatException e) {
        throw new SMSException(e.getMessage());
      }
    } else if (args.length > 1) {
      MiscUtil.errorMessage(player, "Unknown view type: " + args[1]);
      return false;
    }

    if (view == null) {
      // see if we can get a view from what the player is looking at or holding
      notFromConsole(player);
      if (player.getItemInHand().getType() == Material.MAP) { // map view
        PermissionsUtils.requirePerms(player, "scrollingmenusign.use.map");
        short mapId = player.getItemInHand().getDurability();
        view = SMSMapView.addMapToMenu(menu, mapId);
      } else {
        try {
          Block b = player.getTargetBlock(null, 3); // sign view ?
          if (b.getType() == Material.WALL_SIGN || b.getType() == Material.SIGN_POST) {
            view = SMSSignView.addSignToMenu(menu, b.getLocation());
          }
        } catch (IllegalStateException e) {
          // ignore
        }
      }
    }

    if (view != null) {
      MiscUtil.statusMessage(
          player,
          String.format(
              "Added &9%s&- view &e%s&- to menu &e%s&-.",
              view.getType(), view.getName(), menu.getName()));
    } else {
      throw new SMSException("Found nothing suitable to add as a menu view");
    }

    return true;
  }