• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 3041
  • Last Modified:

Line Numbering and Syntax Highlighting in a Text Editor

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
rooster_0429
Asked:
rooster_0429
1 Solution
 
john-at-7fffCommented:
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
 
objectsCommented:
Grab the source for jedit and see what techniques it uses:
http://www.jedit.org/
0
 
GhostModCommented:
PAQd, 500 points refunded.

GhostMod
Community Support Moderator
0

Featured Post

Important Lessons on Recovering from Petya

In their most recent webinar, Skyport Systems explores ways to isolate and protect critical databases to keep the core of your company safe from harm.

Tackle projects and never again get stuck behind a technical roadblock.
Join Now