Solved

Line Numbering and Syntax Highlighting in a Text Editor

Posted on 2004-04-14
4
3,028 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
ID: 10825335
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
ID: 10827908
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
ID: 10881763
PAQd, 500 points refunded.

GhostMod
Community Support Moderator
0

Featured Post

Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Java functions are among the best things for programmers to work with as Java sites can be very easy to read and prepare. Java especially simplifies many processes in the coding industry as it helps integrate many forms of technology and different d…
Basic understanding on "OO- Object Orientation" is needed for designing a logical solution to solve a problem. Basic OOAD is a prerequisite for a coder to ensure that they follow the basic design of OO. This would help developers to understand the b…
Viewers will learn about the regular for loop in Java and how to use it. Definition: Break the for loop down into 3 parts: Syntax when using for loops: Example using a for loop:
The viewer will learn how to implement Singleton Design Pattern in Java.

867 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

21 Experts available now in Live!

Get 1:1 Help Now