SWT Java Tutorial

//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]);
    }
  }
}