现在的位置: 首页 > 综合 > 正文

关键字着色

2013年08月06日 ⁄ 综合 ⁄ 共 8183字 ⁄ 字号 评论关闭

写自己的文本编辑器():
高亮关键字

.
高亮的内容:

需要高亮的内容有:

1.
关键字
, public, int, true

2.
运算符
, +, -, *, /

3.
数字

4.
高亮字符串
, "example of string"

5.
高亮单行注释

6.
高亮多行注释

.
实现高亮的核心方法:

StyledDocument.setCharacterAttributes(int offset, int length,
AttributeSet s, boolean replace) 

.
文本编辑器选择.

Java中提供的多行文本编辑器有: JTextComponent, JTextArea, JTextPane, JEditorPane,
都可以使用.
但是因为语法着色中文本要使用多种风格的样式
, 所以这些文本编辑器的document要使用StyledDocument. 

JTextArea使用的是PlainDocument,
document不能进行多种格式的着色.

JTextPane, JEditorPane使用的是StyledDocument,
默认就可以使用

为了实现语法着色,
可以继承自DefaultStyledDocument,
设置其为这些文本编辑器的
documet, 或者也可以直接使用JTextPane, JEditorPane来做.
为了方便,
这里就直接使用
JTextPane.

.
何时进行着色.

当文本编辑器中有字符被插入或者删除时,
文本的内容就发生了变化, 这时检查,
进行着色.

为了监视到文本的内容发生了变化,
要给document添加一个DocumentListener监听器,
在他的removeUpdateinsertUpdate中进行着色处理.

changedUpdate方法在文本的属性例如前景色,
背景色, 字体等风格改变时才会被调用.

@Override

public
void changedUpdate(DocumentEvent e) {

}

@Override

public
void insertUpdate(DocumentEvent e) {

try {

colouring((StyledDocument) e.getDocument(), e.getOffset(), e.getLength());

}
catch
(BadLocationException e1) {

e1.printStackTrace();

}

}

@Override

public
void removeUpdate(DocumentEvent e) {

try {

//
因为删除后光标紧接着影响的单词两边, 所以长度就不需要了

colouring((StyledDocument) e.getDocument(), e.getOffset(), 0);

}
catch
(BadLocationException e1) {

e1.printStackTrace();

}

}

.
着色范围

pos:
指变化前光标的位置.

len:
指变化的字符数.

例如有关键字public, int

单词"publicint",
"public""int"中插入一个空格后变成"public int",
一个单词变成了两个,
这时对
"public" "int"进行着色.

着色范围是publicp的位置和intt的位置加1,
即是pos前面单词开始的下标和pos+len开始单词结束的下标.
所以上例中要着色的范围是"public int". 

提供了方法indexOfWordStart来取得pos前单词开始的下标,
方法indexOfWordEnd来取得pos后单词结束的下标.

public
int indexOfWordStart(Document doc,
int
pos) throws BadLocationException {

//
pos开始向前找到第一个非单词字符.

for (; pos > 0 && isWordCharacter(doc, pos - 1); --pos);

return pos;

}

public
int indexOfWordEnd(Document doc,
int
pos) throws BadLocationException {

//
pos开始向前找到第一个非单词字符.

for (; isWordCharacter(doc, pos); ++pos);

return pos;

}

一个字符是单词的有效字符:
是字母, 数字,
下划线.

public
boolean isWordCharacter(Document doc,
int
pos) throws BadLocationException {

char ch = getCharAt(doc, pos);
// 取得在文档中pos位置处的字符

if (Character.isLetter(ch) || Character.isDigit(ch) || ch ==
'_') { return
true; }

return
false;

}

所以着色的范围是[start, end] :

int start = indexOfWordStart(doc, pos);

int end = indexOfWordEnd(doc, pos + len);

.
关键字着色.

从着色范围的开始下标起进行判断,
如果是以字母开或者下划线开头, 则说明是单词,
那么先取得这个单词, 如果这个单词是关键字,
就进行关键字着色, 如果不是,
就进行普通的着色. 着色完这个单词后,
继续后面的着色处理. 已经着色过的字符,
就不再进行着色了.

public
void colouring(StyledDocument doc,
int
pos, int len)
throws
BadLocationException {

//
取得插入或者删除后影响到的单词.

//
例如"public"b后插入一个空格,
就变成了:"pub lic",
这时就有两个单词要处理
:"pub""lic"

//
这时要取得的范围是pubp前面的位置和licc后面的位置

int start = indexOfWordStart(doc, pos);

int end = indexOfWordEnd(doc, pos + len);

char ch;

while (start < end) {

ch = getCharAt(doc, start);

if (Character.isLetter(ch) || ch ==
'_') {

//
如果是以字母或者下划线开头, 说明是单词

// pos为处理后的最后一个下标

start = colouringWord(doc, start);

}
else
{

//SwingUtilities.invokeLater(new ColouringTask(doc, pos, wordEnd - pos, normalStyle));

++start;

}

}

}

public
int colouringWord(StyledDocument doc,
int
pos) throws BadLocationException {

int wordEnd = indexOfWordEnd(doc, pos);

String word = doc.getText(pos, wordEnd - pos);
// 要进行着色的单词

if (keywords.contains(word)) {

//
如果是关键字, 就进行关键字的着色,
否则使用普通的着色.

//
这里有一点要注意,
insertUpdateremoveUpdate的方法调用的过程中,
不能修改doc的属性.

//
但我们又要达到能够修改doc的属性,
所以把此任务放到这个方法的外面去执行.

//
实现这一目的, 可以使用新线程,
但放到swing的事件队列里去处理更轻便一点.

SwingUtilities.invokeLater(new ColouringTask(doc, pos, wordEnd - pos,
keywordStyle));

}
else
{

SwingUtilities.invokeLater(new ColouringTask(doc, pos, wordEnd - pos,
normalStyle));

}

return wordEnd;

}

因为在insertUpdateremoveUpdate方法中不能修改document的属性,
所以着色的任务放到这两个方法外面,
所以使用了
SwingUtilities.invokeLater来实现.

private
class ColouringTask
implements
Runnable {

private StyledDocument
doc;

private
Style
style
;

private
int pos;

private
int len;

public ColouringTask(StyledDocument doc,
int pos, int len, Style style) {

this.doc = doc;

this.pos = pos;

this.len = len;

this.style = style;

}

public
void run() {

try {

//
这里就是对字符进行着色

doc.setCharacterAttributes(pos,
len, style,
true);

}
catch
(Exception e) {}

}

}

:
源码

关键字着色的完成代码如下,
可以直接编译运行. 对于数字,
运算符, 字符串等的着色处理在以后的教程中会继续进行详解.

import java.awt.Color;

import java.util.HashSet;

import java.util.Set;

import javax.swing.JFrame;

import javax.swing.JTextPane;

import javax.swing.SwingUtilities;

import javax.swing.event.DocumentEvent;

import javax.swing.event.DocumentListener;

import javax.swing.text.BadLocationException;

import javax.swing.text.Document;

import javax.swing.text.Style;

import javax.swing.text.StyleConstants;

import javax.swing.text.StyledDocument;

public
class HighlightKeywordsDemo {

public
static void main(String[] args) {

JFrame frame =
new JFrame();

JTextPane editor =
new JTextPane();

editor.getDocument().addDocumentListener(new SyntaxHighlighter(editor));

frame.getContentPane().add(editor);

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.setSize(500, 500);

frame.setVisible(true);

}

}

/**

 *
当文本输入区的有字符插入或者删除时, 进行高亮.

 * 

 *
要进行语法高亮
, 文本输入组件的document要是styled document才行.
所以不要用JTextArea.
可以使用
JTextPane.

 * 

 *
@author
Biao

 * 

 */

class SyntaxHighlighter
implements DocumentListener {

private Set<String>
keywords;

private Style
keywordStyle;

private Style
normalStyle;

public SyntaxHighlighter(JTextPane editor) {

//
准备着色使用的样式

keywordStyle = ((StyledDocument) editor.getDocument()).addStyle("Keyword_Style",
null);

normalStyle = ((StyledDocument) editor.getDocument()).addStyle("Keyword_Style",
null);

StyleConstants.setForeground(keywordStyle, Color.RED);

StyleConstants.setForeground(normalStyle, Color.BLACK);

//
准备关键字

keywords =
new HashSet<String>();

keywords.add("public");

keywords.add("protected");

keywords.add("private");

keywords.add("_int9");

keywords.add("float");

keywords.add("double");

}

public
void colouring(StyledDocument doc,
int
pos, int len)
throws
BadLocationException {

//
取得插入或者删除后影响到的单词.

//
例如"public"b后插入一个空格,
就变成了:"pub lic",
这时就有两个单词要处理
:"pub""lic"

//
这时要取得的范围是pubp前面的位置和licc后面的位置

int start = indexOfWordStart(doc, pos);

int end = indexOfWordEnd(doc, pos + len);

char ch;

while (start < end) {

ch = getCharAt(doc, start);

if (Character.isLetter(ch) || ch ==
'_') {

//
如果是以字母或者下划线开头, 说明是单词

// pos为处理后的最后一个下标

start = colouringWord(doc, start);

}
else
{

SwingUtilities.invokeLater(new ColouringTask(doc, start, 1,
normalStyle));

++start;

}

}

}

/**

*
对单词进行着色, 并返回单词结束的下标.

*
@param doc

*
@param pos

*
@return

*
@throws BadLocationException

*/

public
int colouringWord(StyledDocument doc,
int
pos) throws BadLocationException {

int wordEnd = indexOfWordEnd(doc, pos);

String word = doc.getText(pos, wordEnd - pos);

if (keywords.contains(word)) {

//
如果是关键字, 就进行关键字的着色,
否则使用普通的着色.

//
这里有一点要注意,
insertUpdateremoveUpdate的方法调用的过程中,
不能修改doc的属性.

//
但我们又要达到能够修改doc的属性,
所以把此任务放到这个方法的外面去执行.

//
实现这一目的, 可以使用新线程,
但放到swing的事件队列里去处理更轻便一点.

SwingUtilities.invokeLater(new ColouringTask(doc, pos, wordEnd - pos,
keywordStyle));

}
else
{

SwingUtilities.invokeLater(new ColouringTask(doc, pos, wordEnd - pos,
normalStyle));

}

return wordEnd;

}

/**

*
取得在文档中下标在pos处的字符.

*
如果posdoc.getLength(),
返回的是一个文档的结束符,
不会抛出异常
. 如果pos<0,
则会抛出异常
.

*
所以pos的有效值是[0, doc.getLength()]

*
@param doc

*
@param pos

*
@return

*
@throws BadLocationException

*/

public
char getCharAt(Document doc,
int
pos) throws BadLocationException {

return doc.getText(pos, 1).charAt(0);

}

/**

*
取得下标为pos,
它所在的单词开始的下标. ±wor^d± (^表示pos, ±表示开始或结束的下标)

*
@param doc

*
@param pos

*
@return

*
@throws BadLocationException

*/

public
int indexOfWordStart(Document doc,
int
pos) throws BadLocationException {

//
pos开始向前找到第一个非单词字符.

for (; pos > 0 && isWordCharacter(doc, pos - 1); --pos);

return pos;

}

/**

*
取得下标为pos,
它所在的单词结束的下标. ±wor^d± (^表示pos, ±表示开始或结束的下标)

*
@param doc

*
@param pos

*
@return

*
@throws BadLocationException

*/

public
int indexOfWordEnd(Document doc,
int
pos) throws BadLocationException {

//
pos开始向前找到第一个非单词字符.

for (; isWordCharacter(doc, pos); ++pos);

return pos;

}

/**

*
如果一个字符是字母, 数字,
下划线, 则返回true.

*
@param doc

*
@param pos

*
@return

*
@throws BadLocationException

*/

public
boolean isWordCharacter(Document doc,
int
pos) throws BadLocationException {

char ch = getCharAt(doc, pos);

if (Character.isLetter(ch) || Character.isDigit(ch) || ch ==
'_') { return
true; }

return
false;

}

@Override

public
void changedUpdate(DocumentEvent e) {

}

@Override

public
void insertUpdate(DocumentEvent e) {

try {

colouring((StyledDocument) e.getDocument(), e.getOffset(), e.getLength());

}
catch
(BadLocationException e1) {

e1.printStackTrace();

}

}

@Override

public
void removeUpdate(DocumentEvent e) {

try {

//
因为删除后光标紧接着影响的单词两边, 所以长度就不需要了

colouring((StyledDocument) e.getDocument(), e.getOffset(), 0);

}
catch
(BadLocationException e1) {

e1.printStackTrace();

}

}

/**

*
完成着色任务

*
@author Biao

*/

private
class ColouringTask
implements
Runnable {

private StyledDocument
doc;

private Style
style;

private
int pos;

private
int len;

public ColouringTask(StyledDocument doc,
int pos, int len, Style style) {

this.doc = doc;

this.pos = pos;

this.len = len;

this.style = style;

}

public
void run() {

try {

//
这里就是对字符进行着色

doc.setCharacterAttributes(pos,
len, style,
true);

}
catch
(Exception e) {}

}

}

}

抱歉!评论已关闭.