Solved

Line Numbering and Syntax Highlighting in a Text Editor

Posted on 2004-04-14
4
3,025 Views
Last Modified: 2013-12-13
This question is a two part. First is we are trying to syntax highlighting to a text editor. The class we are using is listed below. This code works when we are creating a new file. However when we open an existing file it does not highlight any text. This code is specific to the syntax highlighting portion of our program. If you need to see any of the other classes please just request them.

import javax.swing.text.*;
import java.util.*;
import java.awt.*;
import javax.swing.text.AbstractDocument;

public class CodeDocument extends DefaultStyledDocument{
  private String word = "";
  private SimpleAttributeSet keyword = new SimpleAttributeSet();
  private SimpleAttributeSet string = new SimpleAttributeSet();
  private SimpleAttributeSet normal = new SimpleAttributeSet();
  private SimpleAttributeSet number = new SimpleAttributeSet();
  private SimpleAttributeSet comments = new SimpleAttributeSet();
  private int currentPos = 0;
  private Vector keywords = new Vector();
  public static int STRING_MODE = 10;
  public static int TEXT_MODE = 11;
  public static int NUMBER_MODE = 12;
  public static int COMMENT_MODE = 13;
  private int mode = TEXT_MODE;
 

  public CodeDocument() {
   //set the bold attribute
   StyleConstants.setBold(keyword, true);
   StyleConstants.setForeground(string, Color.magenta);
   StyleConstants.setForeground(number, Color.orange);
   StyleConstants.setForeground(comments, Color.blue);
   StyleConstants.setForeground(keyword,Color.red);
   StyleConstants.setItalic(comments, true);
  }
 

  private void insertKeyword(String str, int pos){
    try{
      //remove the old word and formatting
      this.remove(pos - str.length(), str.length());
      /*replace it with the same word, but new formatting
      *we MUST call the super class insertString method here, otherwise we
      *would end up in an infinite loop !!!!!*/
      super.insertString(pos - str.length(), str, keyword);
    }
    catch (Exception ex){
      ex.printStackTrace();
    }
  }
 

  private void insertTextString(String str, int pos){
    try{
      //remove the old word and formatting
      this.remove(pos,str.length());
      super.insertString(pos, str, string);
    }
    catch (Exception ex){
      ex.printStackTrace();
    }
  }
 

  private void insertNumberString(String str, int pos){
    try{
      //remove the old word and formatting
      this.remove(pos,str.length());
      super.insertString(pos, str, number);
    }
    catch (Exception ex){
      ex.printStackTrace();
    }
  }
 

  private void insertCommentString(String str, int pos){
    try{
      //remove the old word and formatting
      this.remove(pos,str.length());
      super.insertString(pos, str, comments);
    }
    catch (Exception ex){
      ex.printStackTrace();
    }
  }
 

  private void checkForString(){
    int offs = this.currentPos;
    Element element = this.getParagraphElement(offs);
    String elementText = "";
    try{
      //this gets our chuck of current text for the element we're on
      elementText = this.getText(element.getStartOffset(),
                                 element.getEndOffset() -
element.getStartOffset());
    }
    catch(Exception ex){
      //whoops!
      System.out.println("no text");
    }
    int strLen = elementText.length();
    if (strLen == 0) {return;}
    int i = 0;
 

    if (element.getStartOffset() > 0){
      //translates backward if neccessary
      offs = offs - element.getStartOffset();
    }
    int quoteCount = 0;
    if ((offs >= 0) && (offs <= strLen-1)){
      i = offs;
      while (i >0){
      //the while loop walks back until we hit a delimiter
 

        char charAt = elementText.charAt(i);
        if ((charAt == '"')){
         quoteCount ++;
        }
        i--;
      }
      int rem = quoteCount % 2;
      //System.out.println(rem);
      mode = (rem == 0) ? TEXT_MODE: STRING_MODE;
    }
  }
 

  private void checkForKeyword(){
    if (mode != TEXT_MODE) {
      return;
    }
    int offs = this.currentPos;
    Element element = this.getParagraphElement(offs);
    String elementText = "";
    try{
      //this gets our chuck of current text for the element we're on
      elementText = this.getText(element.getStartOffset(),
element.getEndOffset() - element.getStartOffset());
    }
    catch(Exception ex){
      //whoops!
      System.out.println("no text");
    }
    int strLen = elementText.length();
    if (strLen == 0) {return;}
    int i = 0;
 

    if (element.getStartOffset() > 0){
      //translates backward if neccessary
      offs = offs - element.getStartOffset();
    }
    if ((offs >= 0) && (offs <= strLen-1)){
      i = offs;
      while (i >0){
      //the while loop walks back until we hit a delimiter
        i--;
        char charAt = elementText.charAt(i);
        if ((charAt == ' ') | (i == 0) | (charAt =='(') | (charAt ==')') |
            (charAt == '{') | (charAt == '}')){ //if i == 0 then we're atthe begininng
          if(i != 0){
            i++;
          }
          word = elementText.substring(i, offs);//skip the period
 

          String s = word.trim().toLowerCase();
          //this is what actually checks for a matching keyword
          if (keywords.contains(s)){
            insertKeyword(word, currentPos);
          }
          break;
        }
      }
    }
  }
 

  private void checkForNumber(){
    int offs = this.currentPos;
    Element element = this.getParagraphElement(offs);
    String elementText = "";
    try{
      //this gets our chuck of current text for the element we're on
      elementText = this.getText(element.getStartOffset(),
element.getEndOffset() - element.getStartOffset());
    }
    catch(Exception ex){
      //whoops!
      System.out.println("no text");
    }
    int strLen = elementText.length();
    if (strLen == 0) {return;}
    int i = 0;
 

    if (element.getStartOffset() > 0){
      //translates backward if neccessary
      offs = offs - element.getStartOffset();
    }
    mode = TEXT_MODE;
    if ((offs >= 0) && (offs <= strLen-1)){
      i = offs;
      while (i >0){
      //the while loop walks back until we hit a delimiter
        char charAt = elementText.charAt(i);
        if ((charAt == ' ') | (i == 0) | (charAt =='(') | (charAt ==')') |
            (charAt == '{') | (charAt == '}') /*|*/){ //if i == 0 then we're at the begininng
          if(i != 0){
            i++;
          }
          mode = NUMBER_MODE;
          break;
        }
        else if (!(charAt >= '0' & charAt <= '9' | charAt=='.'
                  | charAt=='+' | charAt=='-'
                  | charAt=='/' | charAt=='*'| charAt=='%' | charAt=='=')){
          mode = TEXT_MODE;
          break;
        }
        i--;
      }
    }
  }
 

  private void checkForComment(){
    int offs = this.currentPos;
    Element element = this.getParagraphElement(offs);
    String elementText = "";
    try{
      //this gets our chuck of current text for the element we're on
      elementText = this.getText(element.getStartOffset(),
element.getEndOffset() - element.getStartOffset());
    }
    catch(Exception ex){
      //whoops!
      System.out.println("no text");
    }
    int strLen = elementText.length();
    if (strLen == 0) {return;}
    int i = 0;
 

    if (element.getStartOffset() > 0){
      //translates backward if neccessary
      offs = offs - element.getStartOffset();
    }
    if ((offs >= 1) && (offs <= strLen-1)){
      i = offs;
      char commentStartChar1 = elementText.charAt(i-1);
      char commentStartChar2 = elementText.charAt(i);
      if ((commentStartChar1 == '/' && commentStartChar2 == '*')){
          mode = COMMENT_MODE;
          this.insertCommentString("/*", currentPos-1);
      }
      else if (commentStartChar1 == '*' && commentStartChar2 == '/'){
          mode = TEXT_MODE;
          this.insertCommentString("*/", currentPos-1);
      }
    }
  }
 

  private void processChar(String str){
    char strChar = str.charAt(0);
    if (mode != this.COMMENT_MODE){
      mode = TEXT_MODE;
    }
      switch (strChar){
        case ('{'):case ('}'):case (' '): case('\n'):
        case ('('):case (')'):case (';'):case ('.'):{
          checkForKeyword();
          if (mode == STRING_MODE && strChar == '\n'){
            mode = TEXT_MODE;
          }
        }
        break;
        case ('"'):{
          insertTextString(str, currentPos);
          this.checkForString();
        }
        break;
        case ('0'):case ('1'):case ('2'):case ('3'):case ('4'):
        case ('5'):case ('6'):case ('7'):case ('8'):case ('9'):{
          checkForNumber();
        }
        break;
        case ('*'):case ('/'):{
          checkForComment();
        }
        break;
      }
      if (mode == this.TEXT_MODE){
        this.checkForString();
      }
      if (mode == this.STRING_MODE){
        insertTextString(str, this.currentPos);
      }
      else if (mode == this.NUMBER_MODE){
        insertNumberString(str, this.currentPos);
      }
      else if (mode == this.COMMENT_MODE){
        insertCommentString(str, this.currentPos);
      }
 

  }
 

  private void processChar(char strChar){
      char[] chrstr = new char[1];
      chrstr[0] = strChar;
      String str = new String(chrstr);
      processChar(str);
  }
 

  public void insertString(int offs,
                          String str,
                          AttributeSet a) throws BadLocationException{
    super.insertString(offs, str, normal);
 

    int strLen = str.length();
    int endpos = offs + strLen;
    int strpos;
    for (int i=offs;i<endpos;i++){
      currentPos = i;
      strpos = i - offs;
      processChar(str.charAt(strpos));
    }
    currentPos = offs;
  }
 

  public Vector getKeywords(){
    return this.keywords;
  }
 

  public void setKeywords(Vector aKeywordList){
    if (aKeywordList != null){
      this.keywords = aKeywordList;
    }
  }
}


The second part of my question is line numbering in the editor that we are creating. For new documents it appears fine. If we open an existing document then the code overlaps the line numbers. The class file that we are using to do line numbering is below. If you need to see any other files please request them and they will be provided.


import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;

class LNTextPane extends JFrame
{
    public LNTextPane(JTextPane edit)
    {
    System.out.println("inside constructor ");
    edit.setEditorKit(new NumberedEditorKit());
      
    }
}
      class NumberedEditorKit extends StyledEditorKit
      {
          public ViewFactory getViewFactory()
          {
                System.out.println("inside getView factory");
              return new NumberedViewFactory();
              
          }
      }

      class NumberedViewFactory implements ViewFactory
      {
          public View create(Element elem)
          {
              System.out.println("inside create");
              String kind = elem.getName();
              if (kind != null)
                  if (kind.equals(AbstractDocument.ContentElementName)) {
                      return new LabelView(elem);
                  }
                  else if (kind.equals(AbstractDocument.
                                   ParagraphElementName)) {
                  //    return new ParagraphView(elem);
                      return new NumberedParagraphView(elem);
                  }
                  else if (kind.equals(AbstractDocument.
                           SectionElementName)) {
                      return new BoxView(elem, View.Y_AXIS);
                  }
                  else if (kind.equals(StyleConstants.
                           ComponentElementName)) {
                      return new ComponentView(elem);
                  }
                  else if (kind.equals(StyleConstants.IconElementName)) {
                      return new IconView(elem);
                  }
              // default to text display
              return new LabelView(elem);
          }
      }

      class NumberedParagraphView extends ParagraphView
      {
          public short NUMBERS_WIDTH=25;
      
          public NumberedParagraphView(Element e)
          {
              super(e);
              short top = 0;
              short left = 0;
              short bottom = 0;
              short right = 0;
              this.setInsets(top, left,bottom,right);
          }
      
          protected void setInsets(short top, short left, short bottom,short right)
          {
                System.out.println("inside Insets");
                super.setInsets(top,(short)(left+NUMBERS_WIDTH),bottom,right);
          }
      
          public void paintChild(Graphics g, Rectangle r, int n)
          {
              super.paintChild(g, r, n);
              int previousLineCount = getPreviousLineCount();
              int numberX = r.x - getLeftInset();
              int numberY = r.y + r.height - 5;
              g.drawString(Integer.toString(previousLineCount + n + 1),numberX, numberY);
          }
      
          public int getPreviousLineCount()
          {
              int lineCount = 0;
              View parent = this.getParent();
              int count = parent.getViewCount();
              for (int i = 0; i < count; i++) {
                  if (parent.getView(i) == this) {
                      break;
                  }
                  else {
                      lineCount += parent.getView(i).getViewCount();
                  }
              }
              return lineCount;
          }
      }

0
Comment
Question by:rooster_0429
4 Comments
 
LVL 4

Expert Comment

by:john-at-7fff
Comment Utility
Whew. Those are toughies. Good luck.

I've tried to write a custom view for line numbering, but it turned out that it was easier to set a RowHeaderView on a scrollpane. But you may be locked into your custom editor kit / custom view strategy. I feel your pain.

In any case, the RowHeaderView idea looks a bit like this:

(textPane here is actually a JTextArea)

final JScrollPane scrollPane = new JScrollPane(textPane);
rowHeader = new JLabel() {
    public Dimension getPreferredSize() {
        return new Dimension(rhWidth + 10, (int) textPane.getPreferredSize().getHeight());
    }
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setFont(tpFont);
        int i = h - tpFontMetrics.getDescent();
        int row = 1;
        while (i < getHeight()) {
        String s = Integer.toString(row);
        g.drawString(s, 5 + rhWidth - tpFontMetrics.stringWidth(s), i);
        i += h;
        row++;
    }
}
};
scrollPane.setRowHeaderView(rowHeader);

0
 
LVL 92

Expert Comment

by:objects
Comment Utility
Grab the source for jedit and see what techniques it uses:
http://www.jedit.org/
0
 
LVL 1

Accepted Solution

by:
GhostMod earned 0 total points
Comment Utility
PAQd, 500 points refunded.

GhostMod
Community Support Moderator
0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Suggested Solutions

Title # Comments Views Activity
mapBully challenge 6 88
I could not build boost code, 10 50
Python Assistance 7 31
What is the latest versions eclipse neon 2 116
This was posted to the Netbeans forum a Feb, 2010 and I also sent it to Verisign. Who didn't help much in my struggles to get my application signed. ------------------------- Start The idea here is to target your cell phones with the correct…
Are you developing a Java application and want to create Excel Spreadsheets? You have come to the right place, this article will describe how you can create Excel Spreadsheets from a Java Application. For the purposes of this article, I will be u…
Viewers will learn about the different types of variables in Java and how to declare them. Decide the type of variable desired: Put the keyword corresponding to the type of variable in front of the variable name: Use the equal sign to assign a v…
Viewers will learn how to properly install Eclipse with the necessary JDK, and will take a look at an introductory Java program. Download Eclipse installation zip file: Extract files from zip file: Download and install JDK 8: Open Eclipse and …

772 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

14 Experts available now in Live!

Get 1:1 Help Now