//----------------------------------------------------------------------------
// COMPONENT NAME: LPEX Editor
//
// © Copyright IBM Corporation 2004, 2007
// All Rights Reserved.
//
// DESCRIPTION:
// EntabCommand - sample user-defined command (entab)
//----------------------------------------------------------------------------

package com.ibm.lpex.samples;

import com.ibm.lpex.core.LpexCommand;
import com.ibm.lpex.core.LpexView;

/**
 * Sample command <b>entab</b> - compress leading spaces to tabs.
 * It compresses leading blanks in the document to tab characters.  By default, it
 * uses the value of the <b>tabs</b> editor parameter in the current view.
 * Alternatively, the tab settings may be specified via the command parameters.
 *
 * <p>Here is the EntabCommand
 * <a href="doc-files/EntabCommand.java.html">source code</a>.
 * This class shares several methods with {@link DetabCommand}.</p>
 *
 * <p>To run this sample:
 * <ul>
 *  <li>Define this user command via an editor preference page, where available,
 *   or from the editor command line:
 *   <pre>set commandClass.entab com.ibm.lpex.samples.EntabCommand</pre></li>
 *  <li>Run it from the editor command line:
 *   <pre>entab [<i>stop1 stop2 ..</i> [every <i>increment</i>]]</pre></li>
 * </ul></p>
 *
 * @see com.ibm.lpex.samples.DetabCommand
 * @see com.ibm.lpex.samples All the samples
 */
public class EntabCommand implements LpexCommand
{
 /**
  * Runs this command.
  * Compresses leading spaces in the document to tabs.
  *
  * @param lpexView the document view in which the command was issued
  * @param parameters optional tab stops
  */
 public boolean doCommand(LpexView lpexView, String parameters)
 {
  parameters = parameters.trim();

  if ("?".equals(parameters)) // command help
   {
    if (lpexView != null)
     {
      lpexView.doCommand("set messageText Syntax: entab [<tab stop> .. [every <increment>]]");
     }
    return true;
   }

  // if no view, just validate the parameters (if any)
  if (lpexView == null)
   {
    return DetabCommand.validSettings(parameters);
   }

  // determine and validate the tab settings to use
  DetabCommand.Settings currentSettings = new DetabCommand.Settings();
  if (!DetabCommand.initSettings(lpexView, parameters, currentSettings,
                                 TestCommand.commandName(this, lpexView)))
   {
    return false;
   }

  // compress spaces in the document
  boolean changes = compressBlanks(lpexView, currentSettings);

  // indicate successful completion
  lpexView.doCommand("set messageText " + (changes? "Spaces in document compressed." :
                                                    "No spaces in document compressed."));
  return true;
 }

 /**
  * Compresses leading spaces in the document, according to the given settings.
  *
  * @return indication whether spaces were compressed in the document
  */
 boolean compressBlanks(LpexView lpexView, DetabCommand.Settings currentSettings)
 {
  // remember original cursor location on the screen
  int originalElement = lpexView.currentElement();
  int originalPosition = lpexView.queryInt("displayPosition");

  // process all non-show elements
  boolean changes = false;
  int elements = lpexView.elements();
  for (int element = 1; element <= elements; element++)
   {
    if (!lpexView.show(element))
     {
      if (compressBlanks(element, currentSettings))
       {
        changes = true;
       }
     }
   }

  // restore the cursor
  if (changes && (lpexView.currentElement() != originalElement ||
                  lpexView.queryInt("displayPosition") != originalPosition))
   {
    lpexView.jump(originalElement, 1);
    lpexView.doCommand("set displayPosition " + originalPosition);
   }

  return changes;
 }

 /**
  * Compresses leading spaces in a non-show element, according to the given settings.
  * Tabs are interpreted in terms of display columns.
  *
  * <p>Currently, we change the entire element text - could be subtler and
  * replace one blank with '\t' and then delete any additional blanks up to the
  * tab stop, in order to maintain marks better.</p>
  *
  * @return indication whether spaces were compressed in the element
  */
 boolean compressBlanks(int element, DetabCommand.Settings currentSettings)
 {
  LpexView lpexView = currentSettings._lpexView;
  String text = lpexView.elementText(element);
  if (text.indexOf(' ') < 0)
   {
    return false; // empty line / no spaces.
   }

  StringBuilder buffer = new StringBuilder(text.length());

  int tc = 0;                    // last-used tab stop in currentSettings._tabStops[]
  int lastTabVal = 0;            // last-used tab stop position

  int i = 0;                     // current index into text
  int pos = 1;                   // current display column position
  int iRest = 0;                 // rest-of-line index in text
  boolean changes = false;       // meaningful changes to the text?
  boolean leadingSpaces = false; // coalesce " \t" into "\t"

  for (; i < text.length(); i++)
   {
    // establish relevant tab stop
    while (tc < currentSettings._tabStops.length && lastTabVal <= pos)
     {
      lastTabVal = currentSettings._tabStops[tc++];
     }
    while (lastTabVal <= pos)
     {
      lastTabVal += currentSettings._tabIncrement; // NB it's non-zero
     }

    char c = text.charAt(i);
    if (c == ' ')
     {
      if (pos == lastTabVal - 1)
       {
        buffer.append('\t');
        iRest = i + 1;
        changes = true;
       }
      else
       {
        leadingSpaces = true;
       }
      pos++;
     }
    else if (c == '\t')
     {
      buffer.append('\t');
      iRest = i + 1;
      if (leadingSpaces)
       {
        changes = true;
       }
      pos = lastTabVal; // we'll be at lastTabVal display column position
     }
    else
     {
      break;
     }
   }

  if (changes)
   {
    if (iRest < text.length())
     {
      buffer.append(text.substring(iRest));
     }
    lpexView.setElementText(element, buffer.toString());
   }

  return changes;
 }
}