A simple editor : StyledText « SWT « Java Tutorial






A simple editor
//Code revised from
/*
The Definitive Guide to SWT and JFace
by Robert Harris and Rob Warner 
Apress 2004
*/
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Stack;
import java.util.StringTokenizer;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ExtendedModifyEvent;
import org.eclipse.swt.custom.ExtendedModifyListener;
import org.eclipse.swt.custom.LineStyleEvent;
import org.eclipse.swt.custom.LineStyleListener;
import org.eclipse.swt.custom.ST;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.custom.StyledTextPrintOptions;
import org.eclipse.swt.events.ArmEvent;
import org.eclipse.swt.events.ArmListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.printing.Printer;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;

public class PmpEditor {
  // The number of operations that can be undone
  private static final int UNDO_LIMIT = 500;

  // Contains a reference to this application
  private static PmpEditor app;

  // Contains a reference to the main window
  private Shell shell;

  // Displays the file
  private StyledText st;

  // The full path of the current file
  private String filename;

  // The font for the StyledText
  private Font font;

  // The label to display statistics
  private Label status;

  // The print options and printer
  private StyledTextPrintOptions options;

  private Printer printer;

  // The stack used to store the undo information
  private Stack changes;

  // Flag to set before performaing an undo, so the undo
  // operation doesn't get stored with the rest of the undo
  // information
  private boolean ignoreUndo = false;

  // Syntax data for the current extension
  private SyntaxData sd;

  // Line style listener
  private PmpeLineStyleListener lineStyleListener;

  /**
   * Gets the reference to this application
   * 
   * @return HexEditor
   */
  public static PmpEditor getApp() {
    return app;
  }

  /**
   * Constructs a PmpEditor
   */
  public PmpEditor() {
    app = this;
    changes = new Stack();

    // Set up the printing options
    options = new StyledTextPrintOptions();
    options.footer = StyledTextPrintOptions.SEPARATOR + StyledTextPrintOptions.PAGE_TAG
        + StyledTextPrintOptions.SEPARATOR + "Confidential";
  }

  /**
   * Runs the application
   */
  public void run() {
    Display display = new Display();
    shell = new Shell(display);
    // Choose a monospaced font
    font = new Font(display, "Terminal", 12, SWT.NONE);

    createContents(shell);
    shell.open();
    while (!shell.isDisposed()) {
      if (!display.readAndDispatch()) {
        display.sleep();
      }
    }
    font.dispose();
    display.dispose();
    if (printer != null)
      printer.dispose();
  }

  /**
   * Creates the main window's contents
   * 
   * @param shell
   *          the main window
   */
  private void createContents(Shell shell) {
    // Set the layout and the menu bar
    shell.setLayout(new FormLayout());
    shell.setMenuBar(new PmpEditorMenu(shell).getMenu());

    // Create the status bar
    status = new Label(shell, SWT.BORDER);
    FormData data = new FormData();
    data.left = new FormAttachment(0, 0);
    data.right = new FormAttachment(100, 0);
    data.bottom = new FormAttachment(100, 0);
    data.height = status.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
    status.setLayoutData(data);

    // Create the styled text
    st = new StyledText(shell, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
    data = new FormData();
    data.left = new FormAttachment(0);
    data.right = new FormAttachment(100);
    data.top = new FormAttachment(0);
    data.bottom = new FormAttachment(status);
    st.setLayoutData(data);

    // Set the font
    st.setFont(font);

    // Add Brief delete next word
    // Use SWT.MOD1 instead of SWT.CTRL for portability
    st.setKeyBinding('k' | SWT.MOD1, ST.DELETE_NEXT);

    // Add vi end of line (kind of)
    // Use SWT.MOD1 instead of SWT.CTRL for portability
    // Use SWT.MOD2 instead of SWT.SHIFT for portability
    // Shift+4 is $
    st.setKeyBinding('4' | SWT.MOD1 | SWT.MOD2, ST.LINE_END);

    // Handle key presses
    st.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent event) {
        // Update the status bar
        updateStatus();
      }
    });

    // Handle text modifications
    st.addModifyListener(new ModifyListener() {
      public void modifyText(ModifyEvent event) {
        // Update the status bar
        updateStatus();

        // Update the comments
        if (lineStyleListener != null) {
          lineStyleListener.refreshMultilineComments(st.getText());
          st.redraw();
        }
      }
    });

    // Store undo information
    st.addExtendedModifyListener(new ExtendedModifyListener() {
      public void modifyText(ExtendedModifyEvent event) {
        if (!ignoreUndo) {
          // Push this change onto the changes stack
          changes.push(new TextChange(event.start, event.length, event.replacedText));
          if (changes.size() > UNDO_LIMIT)
            changes.remove(0);
        }
      }
    });

    // Update the title bar and the status bar
    updateTitle();
    updateStatus();
  }

  /**
   * Opens a file
   */
  public void openFile() {
    FileDialog dlg = new FileDialog(shell);
    String temp = dlg.open();
    if (temp != null) {
      try {
        // Get the file's contents
        String text = PmpeIoManager.getFile(temp);
        // File loaded, so save the file name
        filename = temp;

        // Update the syntax properties to use
        updateSyntaxData();

        // Put the new file's data in the StyledText
        st.setText(text);

        // Update the title bar
        updateTitle();

        // Delete any undo information
        changes.clear();
      } catch (IOException e) {
        showError(e.getMessage());
      }
    }
  }

  /**
   * Saves a file
   */
  public void saveFile() {
    if (filename == null) {
      saveFileAs();
    } else {
      try {
        // Save the file and update the title bar based on the new file name
        PmpeIoManager.saveFile(filename, st.getText().getBytes());
        updateTitle();
      } catch (IOException e) {
        showError(e.getMessage());
      }
    }
  }

  /**
   * Saves a file under a different name
   */
  public void saveFileAs() {
    FileDialog dlg = new FileDialog(shell);
    if (filename != null) {
      dlg.setFileName(filename);
    }
    String temp = dlg.open();
    if (temp != null) {
      filename = temp;

      // The extension may have changed; update the syntax data accordingly
      updateSyntaxData();
      saveFile();
    }
  }

  /**
   * Prints the document to the default printer
   */
  public void print() {
    if (printer == null)
      printer = new Printer();
    options.header = StyledTextPrintOptions.SEPARATOR + filename + StyledTextPrintOptions.SEPARATOR;
    st.print(printer, options).run();
  }

  /**
   * Cuts the current selection to the clipboard
   */
  public void cut() {
    st.cut();
  }

  /**
   * Copies the current selection to the clipboard
   */
  public void copy() {
    st.copy();
  }

  /**
   * Pastes the clipboard's contents
   */
  public void paste() {
    st.paste();
  }

  /**
   * Selects all the text
   */
  public void selectAll() {
    st.selectAll();
  }

  /**
   * Undoes the last change
   */
  public void undo() {
    // Make sure undo stack isn't empty
    if (!changes.empty()) {
      // Get the last change
      TextChange change = (TextChange) changes.pop();

      // Set the flag. Otherwise, the replaceTextRange call will get placed
      // on the undo stack
      ignoreUndo = true;
      // Replace the changed text
      st.replaceTextRange(change.getStart(), change.getLength(), change.getReplacedText());

      // Move the caret
      st.setCaretOffset(change.getStart());

      // Scroll the screen
      st.setTopIndex(st.getLineAtOffset(change.getStart()));
      ignoreUndo = false;
    }
  }

  /**
   * Toggles word wrap
   */
  public void toggleWordWrap() {
    st.setWordWrap(!st.getWordWrap());
  }

  /**
   * Gets the current word wrap settings
   * 
   * @return boolean
   */
  public boolean getWordWrap() {
    return st.getWordWrap();
  }

  /**
   * Shows an about box
   */
  public void about() {
    MessageBox mb = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
    mb.setMessage("Poor Man's Programming Editor");
    mb.open();
  }

  /**
   * Updates the title bar
   */
  private void updateTitle() {
    String fn = filename == null ? "Untitled" : filename;
    shell.setText(fn + " -- PmPe");
  }

  /**
   * Updates the status bar
   */
  private void updateStatus() {
    // Show the offset into the file, the total number of characters in the
    // file,
    // the current line number (1-based) and the total number of lines
    StringBuffer buf = new StringBuffer();
    buf.append("Offset: ");
    buf.append(st.getCaretOffset());
    buf.append("\tChars: ");
    buf.append(st.getCharCount());
    buf.append("\tLine: ");
    buf.append(st.getLineAtOffset(st.getCaretOffset()) + 1);
    buf.append(" of ");
    buf.append(st.getLineCount());
    status.setText(buf.toString());
  }

  /**
   * Updates the syntax data based on the filename's extension
   */
  private void updateSyntaxData() {
    // Determine the extension of the current file
    String extension = "";
    if (filename != null) {
      int pos = filename.lastIndexOf(".");
      if (pos > -1 && pos < filename.length() - 2) {
        extension = filename.substring(pos + 1);
      }
    }

    // Get the syntax data for the extension
    sd = SyntaxManager.getSyntaxData(extension);

    // Reset the line style listener
    if (lineStyleListener != null) {
      st.removeLineStyleListener(lineStyleListener);
    }
    lineStyleListener = new PmpeLineStyleListener(sd);
    st.addLineStyleListener(lineStyleListener);

    // Redraw the contents to reflect the new syntax data
    st.redraw();
  }

  /**
   * Shows an error message
   * 
   * @param error
   *          the text to show
   */
  private void showError(String error) {
    MessageBox mb = new MessageBox(shell, SWT.ICON_ERROR | SWT.OK);
    mb.setMessage(error);
    mb.open();
  }

  /**
   * The application entry point
   * 
   * @param args
   *          the command line arguments
   */
  public static void main(String[] args) {
    new PmpEditor().run();
  }
}

class PmpEditorMenu {
  // The underlying menu this class wraps
  Menu menu = null;

  /**
   * Constructs a PmpEditorMenu
   * 
   * @param shell
   *          the parent shell
   */
  public PmpEditorMenu(final Shell shell) {
    // Create the menu
    menu = new Menu(shell, SWT.BAR);

    // Create the File top-level menu
    MenuItem item = new MenuItem(menu, SWT.CASCADE);
    item.setText("File");
    Menu dropMenu = new Menu(shell, SWT.DROP_DOWN);
    item.setMenu(dropMenu);

    // Create File->Open
    item = new MenuItem(dropMenu, SWT.NULL);
    item.setText("Open...\tCtrl+O");
    item.setAccelerator(SWT.CTRL + 'O');
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        PmpEditor.getApp().openFile();
      }
    });

    // Create File->Save
    item = new MenuItem(dropMenu, SWT.NULL);
    item.setText("Save\tCtrl+S");
    item.setAccelerator(SWT.CTRL + 'S');
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        PmpEditor.getApp().saveFile();
      }
    });

    // Create File->Save As
    item = new MenuItem(dropMenu, SWT.NULL);
    item.setText("Save As...");
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        PmpEditor.getApp().saveFileAs();
      }
    });

    new MenuItem(dropMenu, SWT.SEPARATOR);

    // Create File->Print
    item = new MenuItem(dropMenu, SWT.NULL);
    item.setText("Print\tCtrl+P");
    item.setAccelerator(SWT.CTRL + 'P');
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        PmpEditor.getApp().print();
      }
    });

    new MenuItem(dropMenu, SWT.SEPARATOR);

    // Create File->Exit
    item = new MenuItem(dropMenu, SWT.NULL);
    item.setText("Exit\tAlt+F4");
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        shell.close();
      }
    });

    // Create Edit
    item = new MenuItem(menu, SWT.CASCADE);
    item.setText("Edit");
    dropMenu = new Menu(shell, SWT.DROP_DOWN);
    item.setMenu(dropMenu);

    // Create Edit->Cut
    item = new MenuItem(dropMenu, SWT.NULL);
    item.setText("Cut\tCtrl+X");
    item.setAccelerator(SWT.CTRL + 'X');
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        PmpEditor.getApp().cut();
      }
    });

    // Create Edit->Copy
    item = new MenuItem(dropMenu, SWT.NULL);
    item.setText("Copy\tCtrl+C");
    item.setAccelerator(SWT.CTRL + 'C');
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        PmpEditor.getApp().copy();
      }
    });

    // Create Edit->Paste
    item = new MenuItem(dropMenu, SWT.NULL);
    item.setText("Paste\tCtrl+V");
    item.setAccelerator(SWT.CTRL + 'V');
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        PmpEditor.getApp().paste();
      }
    });

    new MenuItem(dropMenu, SWT.SEPARATOR);

    // Create Select All
    item = new MenuItem(dropMenu, SWT.NULL);
    item.setText("Select All\tCtrl+A");
    item.setAccelerator(SWT.CTRL + 'A');
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        PmpEditor.getApp().selectAll();
      }
    });

    new MenuItem(dropMenu, SWT.SEPARATOR);

    // Create Undo
    item = new MenuItem(dropMenu, SWT.NULL);
    item.setText("Undo\tCtrl+Z");
    item.setAccelerator(SWT.CTRL + 'Z');
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        PmpEditor.getApp().undo();
      }
    });

    new MenuItem(dropMenu, SWT.SEPARATOR);
    // Create Word Wrap
    final MenuItem wwItem = new MenuItem(dropMenu, SWT.CHECK);
    wwItem.setText("Word Wrap\tCtrl+W");
    wwItem.setAccelerator(SWT.CTRL + 'W');
    wwItem.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        PmpEditor.getApp().toggleWordWrap();
      }
    });
    wwItem.addArmListener(new ArmListener() {
      public void widgetArmed(ArmEvent event) {
        wwItem.setSelection(PmpEditor.getApp().getWordWrap());
      }
    });

    // Create Help
    item = new MenuItem(menu, SWT.CASCADE);
    item.setText("Help");
    dropMenu = new Menu(shell, SWT.DROP_DOWN);
    item.setMenu(dropMenu);

    // Create Help->About
    item = new MenuItem(dropMenu, SWT.NULL);
    item.setText("About\tCtrl+A");
    item.setAccelerator(SWT.CTRL + 'A');
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        PmpEditor.getApp().about();
      }
    });
  }

  /**
   * Gets the underlying menu
   * 
   * @return Menu
   */
  public Menu getMenu() {
    return menu;
  }
}

class PmpeIoManager {
  /**
   * Gets a file (loads it) from the filesystem
   * 
   * @param filename
   *          the full path of the file
   * @return String
   * @throws IOException
   *           if file cannot be loaded
   */
  public static String getFile(String filename) throws IOException {
    InputStream in = new BufferedInputStream(new FileInputStream(filename));
    StringBuffer buf = new StringBuffer();
    int c;
    while ((c = in.read()) != -1) {
      buf.append((char) c);
    }
    return buf.toString();
  }

  /**
   * Saves a file
   * 
   * @param filename
   *          the full path of the file to save
   * @param data
   *          the data to save
   * @throws IOException
   *           if file cannot be saved
   */
  public static void saveFile(String filename, byte[] data) throws IOException {
    File outputFile = new File(filename);
    FileOutputStream out = new FileOutputStream(outputFile);
    out.write(data);
    out.close();
  }
}

class TextChange {
  // The starting offset of the change
  private int start;

  // The length of the change
  private int length;

  // The replaced text
  String replacedText;

  /**
   * Constructs a TextChange
   * 
   * @param start
   *          the starting offset of the change
   * @param length
   *          the length of the change
   * @param replacedText
   *          the text that was replaced
   */
  public TextChange(int start, int length, String replacedText) {
    this.start = start;
    this.length = length;
    this.replacedText = replacedText;
  }

  /**
   * Returns the start
   * 
   * @return int
   */
  public int getStart() {
    return start;
  }

  /**
   * Returns the length
   * 
   * @return int
   */
  public int getLength() {
    return length;
  }

  /**
   * Returns the replacedText
   * 
   * @return String
   */
  public String getReplacedText() {
    return replacedText;
  }
}

/**
 * This class contains information for syntax coloring and styling for an
 * extension
 */
class SyntaxData {
  private String extension;

  private Collection keywords;

  private String punctuation;

  private String comment;

  private String multiLineCommentStart;

  private String multiLineCommentEnd;

  /**
   * Constructs a SyntaxData
   * 
   * @param extension
   *          the extension
   */
  public SyntaxData(String extension) {
    this.extension = extension;
  }

  /**
   * Gets the extension
   * 
   * @return String
   */
  public String getExtension() {
    return extension;
  }

  /**
   * Gets the comment
   * 
   * @return String
   */
  public String getComment() {
    return comment;
  }

  /**
   * Sets the comment
   * 
   * @param comment
   *          The comment to set.
   */
  public void setComment(String comment) {
    this.comment = comment;
  }

  /**
   * Gets the keywords
   * 
   * @return Collection
   */
  public Collection getKeywords() {
    return keywords;
  }

  /**
   * Sets the keywords
   * 
   * @param keywords
   *          The keywords to set.
   */
  public void setKeywords(Collection keywords) {
    this.keywords = keywords;
  }

  /**
   * Gets the multiline comment end
   * 
   * @return String
   */
  public String getMultiLineCommentEnd() {
    return multiLineCommentEnd;
  }

  /**
   * Sets the multiline comment end
   * 
   * @param multiLineCommentEnd
   *          The multiLineCommentEnd to set.
   */
  public void setMultiLineCommentEnd(String multiLineCommentEnd) {
    this.multiLineCommentEnd = multiLineCommentEnd;
  }

  /**
   * Gets the multiline comment start
   * 
   * @return String
   */
  public String getMultiLineCommentStart() {
    return multiLineCommentStart;
  }

  /**
   * Sets the multiline comment start
   * 
   * @param multiLineCommentStart
   *          The multiLineCommentStart to set.
   */
  public void setMultiLineCommentStart(String multiLineCommentStart) {
    this.multiLineCommentStart = multiLineCommentStart;
  }

  /**
   * Gets the punctuation
   * 
   * @return String
   */
  public String getPunctuation() {
    return punctuation;
  }

  /**
   * Sets the punctuation
   * 
   * @param punctuation
   *          The punctuation to set.
   */
  public void setPunctuation(String punctuation) {
    this.punctuation = punctuation;
  }
}

/**
 * This class manages the syntax coloring and styling data
 */
class SyntaxManager {
  // Lazy cache of SyntaxData objects
  private static Map data = new Hashtable();

  /**
   * Gets the syntax data for an extension
   */
  public static synchronized SyntaxData getSyntaxData(String extension) {
    // Check in cache
    SyntaxData sd = (SyntaxData) data.get(extension);
    if (sd == null) {
      // Not in cache; load it and put in cache
      sd = loadSyntaxData(extension);
      if (sd != null)
        data.put(sd.getExtension(), sd);
    }
    return sd;
  }

  /**
   * Loads the syntax data for an extension
   * 
   * @param extension
   *          the extension to load
   * @return SyntaxData
   */
  private static SyntaxData loadSyntaxData(String extension) {
    SyntaxData sd = null;
    try {
      ResourceBundle rb = ResourceBundle.getBundle("examples.ch11." + extension);
      sd = new SyntaxData(extension);
      sd.setComment(rb.getString("comment"));
      sd.setMultiLineCommentStart(rb.getString("multilinecommentstart"));
      sd.setMultiLineCommentEnd(rb.getString("multilinecommentend"));

      // Load the keywords
      Collection keywords = new ArrayList();
      for (StringTokenizer st = new StringTokenizer(rb.getString("keywords"), " "); st
          .hasMoreTokens();) {
        keywords.add(st.nextToken());
      }
      sd.setKeywords(keywords);

      // Load the punctuation
      sd.setPunctuation(rb.getString("punctuation"));
    } catch (MissingResourceException e) {
      // Ignore
    }
    return sd;
  }
}

/**
 * This class performs the syntax highlighting and styling for Pmpe
 */
class PmpeLineStyleListener implements LineStyleListener {
  // Colors
  private static final Color COMMENT_COLOR = Display.getCurrent().getSystemColor(
      SWT.COLOR_DARK_GREEN);

  private static final Color COMMENT_BACKGROUND = Display.getCurrent().getSystemColor(
      SWT.COLOR_GRAY);

  private static final Color PUNCTUATION_COLOR = Display.getCurrent().getSystemColor(
      SWT.COLOR_DARK_CYAN);

  private static final Color KEYWORD_COLOR = Display.getCurrent().getSystemColor(
      SWT.COLOR_DARK_MAGENTA);

  // Holds the syntax data
  private SyntaxData syntaxData;

  // Holds the offsets for all multiline comments
  List commentOffsets;

  /**
   * PmpeLineStyleListener constructor
   * 
   * @param syntaxData
   *          the syntax data to use
   */
  public PmpeLineStyleListener(SyntaxData syntaxData) {
    this.syntaxData = syntaxData;
    commentOffsets = new LinkedList();
  }

  /**
   * Refreshes the offsets for all multiline comments in the parent StyledText.
   * The parent StyledText should call this whenever its text is modified. Note
   * that this code doesn't ignore comment markers inside strings.
   * 
   * @param text
   *          the text from the StyledText
   */
  public void refreshMultilineComments(String text) {
    // Clear any stored offsets
    commentOffsets.clear();

    if (syntaxData != null) {
      // Go through all the instances of COMMENT_START
      for (int pos = text.indexOf(syntaxData.getMultiLineCommentStart()); pos > -1; pos = text
          .indexOf(syntaxData.getMultiLineCommentStart(), pos)) {
        // offsets[0] holds the COMMENT_START offset
        // and COMMENT_END holds the ending offset
        int[] offsets = new int[2];
        offsets[0] = pos;

        // Find the corresponding end comment.
        pos = text.indexOf(syntaxData.getMultiLineCommentEnd(), pos);

        // If no corresponding end comment, use the end of the text
        offsets[1] = pos == -1 ? text.length() - 1 : pos
            + syntaxData.getMultiLineCommentEnd().length() - 1;
        pos = offsets[1];
        // Add the offsets to the collection
        commentOffsets.add(offsets);
      }
    }
  }

  /**
   * Checks to see if the specified section of text begins inside a multiline
   * comment. Returns the index of the closing comment, or the end of the line
   * if the whole line is inside the comment. Returns -1 if the line doesn't
   * begin inside a comment.
   * 
   * @param start
   *          the starting offset of the text
   * @param length
   *          the length of the text
   * @return int
   */
  private int getBeginsInsideComment(int start, int length) {
    // Assume section doesn't being inside a comment
    int index = -1;

    // Go through the multiline comment ranges
    for (int i = 0, n = commentOffsets.size(); i < n; i++) {
      int[] offsets = (int[]) commentOffsets.get(i);

      // If starting offset is past range, quit
      if (offsets[0] > start + length)
        break;
      // Check to see if section begins inside a comment
      if (offsets[0] <= start && offsets[1] >= start) {
        // It does; determine if the closing comment marker is inside
        // this section
        index = offsets[1] > start + length ? start + length : offsets[1]
            + syntaxData.getMultiLineCommentEnd().length() - 1;
      }
    }
    return index;
  }

  /**
   * Called by StyledText to get styles for a line
   */
  public void lineGetStyle(LineStyleEvent event) {
    // Only do styles if syntax data has been loaded
    if (syntaxData != null) {
      // Create collection to hold the StyleRanges
      List styles = new ArrayList();

      int start = 0;
      int length = event.lineText.length();

      // Check if line begins inside a multiline comment
      int mlIndex = getBeginsInsideComment(event.lineOffset, event.lineText.length());
      if (mlIndex > -1) {
        // Line begins inside multiline comment; create the range
        styles.add(new StyleRange(event.lineOffset, mlIndex - event.lineOffset, COMMENT_COLOR,
            COMMENT_BACKGROUND));
        start = mlIndex;
      }
      // Do punctuation, single-line comments, and keywords
      while (start < length) {
        // Check for multiline comments that begin inside this line
        if (event.lineText.indexOf(syntaxData.getMultiLineCommentStart(), start) == start) {
          // Determine where comment ends
          int endComment = event.lineText.indexOf(syntaxData.getMultiLineCommentEnd(), start);

          // If comment doesn't end on this line, extend range to end of line
          if (endComment == -1)
            endComment = length;
          else
            endComment += syntaxData.getMultiLineCommentEnd().length();
          styles.add(new StyleRange(event.lineOffset + start, endComment - start, COMMENT_COLOR,
              COMMENT_BACKGROUND));

          // Move marker
          start = endComment;
        }
        // Check for single line comments
        else if (event.lineText.indexOf(syntaxData.getComment(), start) == start) {
          // Comment rest of line
          styles.add(new StyleRange(event.lineOffset + start, length - start, COMMENT_COLOR,
              COMMENT_BACKGROUND));

          // Move marker
          start = length;
        }
        // Check for punctuation
        else if (syntaxData.getPunctuation().indexOf(event.lineText.charAt(start)) > -1) {
          // Add range for punctuation
          styles.add(new StyleRange(event.lineOffset + start, 1, PUNCTUATION_COLOR, null));
          ++start;
        } else if (Character.isLetter(event.lineText.charAt(start))) {
          // Get the next word
          StringBuffer buf = new StringBuffer();
          int i = start;
          // Call any consecutive letters a word
          for (; i < length && Character.isLetter(event.lineText.charAt(i)); i++) {
            buf.append(event.lineText.charAt(i));
          }
          // See if the word is a keyword
          if (syntaxData.getKeywords().contains(buf.toString())) {
            // It's a keyword; create the StyleRange
            styles.add(new StyleRange(event.lineOffset + start, i - start, KEYWORD_COLOR, null,
                SWT.BOLD));
          }
          // Move the marker to the last char (the one that wasn't a letter)
          // so it can be retested in the next iteration through the loop
          start = i;
        } else
          // It's nothing we're interested in; advance the marker
          ++start;
      }

      // Copy the StyleRanges back into the event
      event.styles = (StyleRange[]) styles.toArray(new StyleRange[0]);
    }
  }
}








17.42.StyledText
17.42.1.Creating a StyledText Widget
17.42.2.Create a StyledText that scrolls vertically, wraps text, and displays a border:Create a StyledText that scrolls vertically, wraps text, and displays a border:
17.42.3.Using the ClipboardUsing the Clipboard
17.42.4.Draw a box around textDraw a box around text
17.42.5.StyledText: embed imagesStyledText: embed images
17.42.6.StyledText: use gradient backgroundStyledText: use gradient background
17.42.7.StyledText: embed controlsStyledText: embed controls
17.42.8.Getting Statistics: Caret Offset, Total Lines of Text, Total Characters and Current LineGetting Statistics: Caret Offset, Total Lines of Text, Total Characters and Current Line
17.42.9.Print StyledText outPrint StyledText out
17.42.10.Print to the default printer in a separate thread
17.42.11.Set page format for printing
17.42.12.Make a StyledText read-only
17.42.13.Limit the number of characters that the StyledText acceptsLimit the number of characters that the StyledText accepts
17.42.14.Replace Text RangeReplace Text Range
17.42.15.Understanding the Repercussions
17.42.16.A simple editorA simple editor