JAVA.SWT/JFace: JFace篇之文本处理

《Eclipse SWT/JFACE 核心应用》 清华大学出版社 21 文本处理

JFace提供的文本处理功能非常强大,提醒在Eclipse平台中编辑器的部分。例如代码的折叠、内容提示、代码的格式化等,这些都是基于JFace的文本处理框架的。

21.1 文本处理概述
JFace中有关文本处理的部分分别打包在以下两个包中:
◆ org.eclipse.jface.text_3.6.0.v20100526-0800.jar
◆ org.eclipse.text_3.5.0.v20100601-1300.jar

文本的处理在各种语言的编辑器中经常使用,功能主要有语法着色、语法检查、内容辅助提示等,JFace的文本处理框架的功能远不止这些,功能非常强大。

21.2 项目实战:JavaScript编辑器

该编辑器实现的主要功能如下:

◇ 文件的打开、保存和打印。
◇ 可自定义不同关键字的颜色,如注释的颜色、字符串的颜色、JavaScript关键字的颜色和一些JavaScript内置对象的颜色。
◇ 可自定义显示文本的字体格式。
◇ 对文本进行撤销和重复操作。
◇ 可对文本进行查找和替换功能。
◇ 当输入代码的过程中,可以在提示JavaScript语法中内置对象的内容提示功能。

本项目的文件结果:
◆ 本项目包中的类:
◇ Constants.Java:保存了程序中使用的一些字符串常量。
◇ EventManager.java:集中处理事件的类。
◇ JSCodeScanner.java:搜索代码规则的类。
◇ JSEditor.java:程序的主窗口类。
◇ JSEditorConfiguration.java:编辑器配置类。
◇ JSEditorTestDrive.java:程序的入口类。
◇ JSPreferencePage.java:设置首选项页面。
◇ ObjectContentAssistant.java:内容助手类。
◇ ObjectDetector.java:搜索内置对象字符类。
◇ KeywordDetector.java:搜索关键字对象字符类。
◇ PersistentDocument.java:持久化文档类。
◇ ResourceManager.java:程序中涉及的一些资源的管理。
◆ 本项目action包中的类:
◇ HelpAction.java:帮助菜单项。
◇ OpenAction.java:打开文件菜单项。
◇ PreferenceAction.java:打开首选项对话框项。
◇ PrintAction.java:打印菜单项。
◇ RedoAction.java:撤销菜单项。
◇ SaveAction:保存菜单项。
◇ SearchAction.java:查找替换菜单项。
◇ UndoAction.java:重复此次操作菜单项。
◆ 本项目dialog包中的类:
◇ AboutDialog.java:关于对话框。
◇ FindAndReplace.java:查找替换对话框。

21.3 主窗口模块
主窗口模块是本程序的主界面,继承自ApplicationWindow,这是一个应用程序窗口,窗口中还添加了菜单栏和工具栏。

该主窗口程序中显示代码的控件是SourceViewer对象。代码如下:

package www.swt.com.ch21;

import org.eclipse.jface.action.*;

public class JSEditor extends ApplicationWindow implements IPropertyChangeListener {

private PersistentDocument document;//数据文档对象
private SourceViewer viewer;//显示文本控件对象
private EventManager eventManager;//事件管理器
private PreferenceStore preference;//保存首选项设置的对象
private IUndoManager undoManager;//撤销与重复管理器
private JSEditorConfiguration configuration;//文本控件的配置对象
public JSEditor() {
super(null);
eventManager = new EventManager(this);//初始化事件管理器
this.addMenuBar();//添加菜单
this.addToolBar( SWT.FLAT );//添加工具栏
}
protected void configureShell(Shell shell) {
super.configureShell(shell);
shell.setText(“JavaScript 编辑器”);
shell.setSize(600, 400);
}
protected Control createContents(Composite parent) {
Composite top = new Composite(parent,SWT.NONE);
top.setLayout( new FillLayout());
//初始化文档对象
document = new PersistentDocument();
//初始化源文件显示控件对象
viewer = new SourceViewer(top, new VerticalRuler(10), SWT.V_SCROLL | SWT.H_SCROLL);
//初始化文档的配置对象
configuration = new JSEditorConfiguration(this);
viewer.configure(configuration );//设置文档配置
viewer.setDocument(document);//设置文档
undoManager = new DefaultUndoManager(100);//初始化撤销管理器对象,默认可撤销100次
undoManager.connect(viewer);//将该撤销管理器应用于文档
//初始化代码中的字体
initCodeFont();
return parent;
}
//初始化代码的字体
private void initCodeFont() {
// 定义一个默认的字体
FontData defaultFont = new FontData(“Courier New”, 10, SWT.NORMAL);
// 如果从首选项读出的字体有异常,则使用默认的字体
FontData data = StringConverter.asFontData(ResourceManager
.getPreferenceStore().getString(Constants.CODE_FONT),
defaultFont);
// 创建字体
Font font = new Font(this.getShell().getDisplay(), data);
viewer.getTextWidget().setFont(font);// 设置字体
}
//初始化菜单项
protected MenuManager createMenuManager() {
MenuManager top = new MenuManager();
MenuManager fileMenu = new MenuManager(“文件(&F)”);
MenuManager editMenu = new MenuManager(“编辑(&E)”);
MenuManager helpMenu = new MenuManager(“帮助(&H)”);

fileMenu.add( new OpenAction(this) );
fileMenu.add( new SaveAction(this) );
fileMenu.add( new Separator());
fileMenu.add( new PrintAction(this) );

editMenu.add( new UndoAction(this));
editMenu.add( new RedoAction(this));
editMenu.add( new Separator());
editMenu.add( new SearchAction(this));
editMenu.add( new Separator());
editMenu.add( new PreferenceAction(this));

helpMenu.add( new HelpAction(this));
top.add( fileMenu );
top.add( editMenu ) ;
top.add(helpMenu);

return top;
}
//初始化工具栏
protected ToolBarManager createToolBarManager(int style) {
ToolBarManager toolBar = new ToolBarManager(style);
toolBar.add(new OpenAction(this));
toolBar.add( new SaveAction(this) );
toolBar.add( new Separator());
toolBar.add( new PrintAction(this) );

toolBar.add( new UndoAction(this));
toolBar.add( new RedoAction(this));
toolBar.add( new Separator());
toolBar.add( new SearchAction(this));
toolBar.add( new Separator());
toolBar.add( new PreferenceAction(this));
toolBar.add( new HelpAction(this));
return toolBar;
}

public PersistentDocument getDocument() {
return document;
}

public SourceViewer getViewer() {
return viewer;
}

public EventManager getEventManager() {
return eventManager;
}

public PreferenceStore getPreference() {
return preference;
}

public void setPreference(PreferenceStore preference) {
this.preference = preference;
}

public IUndoManager getUndoManager() {
return undoManager;
}

public JSEditorConfiguration getConfiguration() {
return configuration;
}
//为IPropertyChangeListener接口中的方法,当设置首选项的字体时
//重新设置编辑器的字体
public void propertyChange(PropertyChangeEvent event) {
if (event.getNewValue()==null)
return;
if (event.getProperty().equals(Constants.CODE_FONT))
eventManager.setCodeFont( (FontData[]) event.getNewValue());
}
}

主窗口程序代码分析:
◆ TextViewer对象:
TextViewer对象可以理解为是文本处理的MVC对象,TreeView底层封装了Tree控件,使树的数据和视图分开,而TextViewer底层封装的是StyledText对象,用于显示文本,文本的数据部分则是由实现了IDocument接口的对象提供的。创建一个简单的文本编辑器如下:
TextViewer viewer = new TextViewer(shell, SWT.V_SCROLL|SWT.H_SCROLL);
viewer.setDocument(new Document());
在本程序中使用的是SourceViewer对象,该类继承自TextViewer,除了一般的编辑文本的功能外,增加了编辑源代码的功能,适用作代码编辑器。另外,该类还有一个子类ProjectionViewer类,比SourceViewer功能更加丰富,比如说可以提供代码折叠的效果等。

◆ IDocument接口:
处理文本另一个重要的概念就是IDocument接口,它提供了文本的数据,包括文本的修改、字符的位置、行数等属性和操作。实现该接口常用的有Document类和ProjectionDocument类,后者主要是与ProjectionViewer一起使用,通常情况下使用Document类就可以了。

本程序中使用的PersistentDocument类继承自Document类,为文档增加了打开和保存数据的功能。代码如下:

package www.swt.com.ch21;

import java.io.*;
import org.eclipse.jface.text.*;

public class PersistentDocument extends Document implements IDocumentListener {
private String fileName;//打开的文件名
private boolean dirty;//文件是否修改过
public PersistentDocument() {
this.addDocumentListener(this);//为该文档注册文档监听器
}

//保存到指定的文件
public void save() throws IOException {
if (fileName == null)
throw new IllegalStateException(“文件名不为空!”);
BufferedWriter out = null;
try {
out = new BufferedWriter(new FileWriter(fileName));
out.write(get());
dirty = false;
} finally {
try {
if (out != null)
out.close();
} catch (IOException e) {
}
}
}
//打开文件的方法
public void open() throws IOException {
if (fileName == null)
throw new IllegalStateException(“文件名不为空!”);
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(fileName));
StringBuffer buf = new StringBuffer();
int n;
while ((n = in.read()) != -1) {
buf.append((char) n);
}
set(buf.toString());
dirty = false;
} finally {
try {
if (in != null)
in.close();
} catch (IOException e) {
}
}
}
public boolean isDirty() {
return dirty;
}
public void setDirty(boolean dirty) {
this.dirty = dirty;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
//接口中的方法,空实现
public void documentAboutToBeChanged(DocumentEvent arg0) {

}
//接口中的方法,当文档修改后,设置已修改状态
public void documentChanged(DocumentEvent arg0) {
dirty = true;
}
}
对文档中的字符的获取是通过父类的get()方法获得的,而设置文档字符的方法是通过父类中的set()方法设置的。

启动主窗口程序:
在启动主窗口程序之前,首先加载首选项文件,因为代码着色部分要使用首选项保存文件中的对各种颜色的设置。
该启动程序的代码实现如下:

package www.swt.com.ch21;

import java.io.IOException;

public class JSEditorTestDrive {
public static void main(String[] args) {
//创建主窗口对象
JSEditor jsApp = new JSEditor();
//为主窗口对象设置首选项对象
//首选项对象通过ResourceManager中的静态方法获得
jsApp.setPreference(ResourceManager.getPreferenceStore() );
//并为首选项保存对象注册选项改变监听器
ResourceManager.getPreferenceStore().addPropertyChangeListener(jsApp);
jsApp.setBlockOnOpen(true);
jsApp.open();
Display.getCurrent().dispose();
//窗口关闭后保存设置的首选项
try {
ResourceManager.getPreferenceStore().save();
} catch (IOException e) {
e.printStackTrace();
}
}
}

21.4 代码着色
对代码着色要遵循一定的规则,比如说要区别注释和关键字,也要区别声明的字符和内置的一些对象等。

◆ 源代码配置类(SourceViewerConfiguration)
要实现对代码的着色,主要是通过设置SourceView对象中的configure方法来实现的:
viewer.configure(configuration );//设置文档配置
其中,configuration是JSEditorConfiguration配置对象,对代码实现的着色和内容辅助的功能都是通过该对象来设置的。JSEditorConfiguration类继承自SourceViewerConfiguration类,通过覆盖父类中的方法可以实现对代码进行着色和内容辅助等功能。代码如下:
package www.swt.com.ch21;

import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.*;
import org.eclipse.jface.text.presentation.*;
import org.eclipse.jface.text.rules.*;
import org.eclipse.jface.text.source.*;

public class JSEditorConfiguration extends SourceViewerConfiguration {

private JSEditor editor ;
public JSEditorConfiguration( JSEditor editor ){
this.editor=editor;
}
//覆盖父类中的方法,主要提供代码着色功能
public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) {
PresentationReconciler reconciler = new PresentationReconciler();
DefaultDamagerRepairer dr = new DefaultDamagerRepairer(new JSCodeScanner());
reconciler.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE);
reconciler.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE);
return reconciler;
}
//覆盖父类中的方法,主要提供内容辅助功能
public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) {
//创建内容助手对象
ContentAssistant contentAssistant = new ContentAssistant();
//设置提示的内容
contentAssistant.setContentAssistProcessor(new ObjectContentAssistant(),IDocument.DEFAULT_CONTENT_TYPE);
//设置自动激活提示
contentAssistant.enableAutoActivation(true);
//设置自动激活提示的时间为500毫秒
contentAssistant.setAutoActivationDelay(500);
return contentAssistant;
}
}
覆盖父类中的getPresentationReconciler方法,可以设置当输入文本时可处理的一些事件。IPresentationReconciler接口负责处理文件修改的过程,PresentationReconciler实现了该接口。当文本修改时,可以任务当前的文本不再正确。此时,会调用IPresentationDamager对象计算被修改文档所在的区域,而IPresentationRepairer对象将利用新的文本属性来进行修改,所以要使用setDamager和setRepairer方法。
另外DefaultDamagerRepairer对象实现了IPresentationDamager和IPresentationRepairer接口。该对象可以通过设定一下规则进行代码扫描,当扫描的代码符合规则时,就会按照规则指定代码样式修改。

◆ 基于规则的代码扫描器类(RuleBasedScanner)
具体设置代码修复的规则如下:
DefaultDamagerRepairer dr = new DefaultDamagerRepairer(new JSCodeScanner());
在以上的代码中创建了一个JSCodeScanner对象。该对象即为扫描代码的规则对象。代码如下:
package www.swt.com.ch21;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.rules.*;
import org.eclipse.swt.SWT;

public class JSCodeScanner extends RuleBasedScanner {

private TextAttribute keywords ;//关键字的文本属性
private TextAttribute string ;//字符串的文本属性
private TextAttribute object ;//内置对象的文本属性
private TextAttribute comment ;//注释部分的文本属性
public JSCodeScanner(){
//获得首选项中所设置的颜色,初始化各文本属性
keywords = new TextAttribute (ResourceManager.getColor(Constants.COLOR_KEYWORD),null,SWT.BOLD);
string = new TextAttribute (ResourceManager.getColor(Constants.COLOR_STRING));
object = new TextAttribute (ResourceManager.getColor(Constants.COLOR_OBJECT));
comment = new TextAttribute (ResourceManager.getColor(Constants.COLOR_COMMENT),null,SWT.ITALIC);
//设置代码的规则
setupRules();
}
private void setupRules() {
//用一个List集合对象保存所有的规则
List rules = new ArrayList();
//字符串的规则
rules.add(new SingleLineRule(“\”“, “\”“,new Token( string), ‘\’));
rules.add(new SingleLineRule(“’”, “’”, new Token( string), ‘\’));
//注释的规则
rules.add(new SingleLineRule(“/“, “/”, new Token( comment), ‘\’));
rules.add(new EndOfLineRule(“//”, new Token( comment),’\’));
//空格的规则
rules.add(new WhitespaceRule(new IWhitespaceDetector() {
public boolean isWhitespace(char c) {
return Character.isWhitespace(c);
}
}));
//关键字的规则
WordRule keywordRule = new WordRule(new KeywordDetector());
for (int i = 0, n = Constants.JS_SYNTAX_KEYWORD.length; i < n; i++)
keywordRule.addWord(Constants.JS_SYNTAX_KEYWORD[i], new Token( keywords ));
rules.add(keywordRule);
//内置对象的规则
WordRule objectRule = new WordRule(new ObjectDetector());
for (int i = 0, n = Constants.JS_SYNTAX_BUILDIB_OBJECT.length; i < n; i++)
objectRule.addWord(Constants.JS_SYNTAX_BUILDIB_OBJECT[i], new Token( object ));
rules.add(objectRule);
//集合类中保存的规则转化为数组
IRule[] result = new IRule[rules.size()];
rules.toArray(result);
//调用父类中的方法,设置规则
//此方法非常重要
setRules(result);
}
}

使用该类方法的主要目的是,为扫描代码提供多个规则。可以这样理解,当修改文本时,程序会自动搜索所有设置的规则,如果当前的文本符合设置的某一个规则时,就按照这个规则所设定的文字样式重新显示文本。所以,对规则的设定直接影响着如何对不同的代码进行着色。
该类JSCodeScanner继承自RuleBasedScanner,这样就可以使用设置的规则来对代码进行扫描了。

◆ 设置代码扫描规则
所有实现了IRule接口的规则如下:
EndOfLineRule, MultiLineRule, NumberRule, PatternRule, SingleLineRule, WhitespaceRule, WordPatternRule, WordRule

该程序中使用的规则类:
● SingleLineRule:单行规则类
    SingleLineRule可以设置指定两个字符间的规则。它的构造方法有:
     ⊙ SingleLineRule(String startSequence, String endSequence, IToken token) :startSequence表示起始的字符,endSequence表示终止的字符,token为符合该规则时所应用的代码样式IToken对象。
     ⊙ SingleLineRule(String startSequence, String endSequence, IToken token, char escapeCharacter) :escapeCharacter表示转义符。
     ⊙ SingleLineRule(String startSequence, String endSequence, IToken token, char escapeCharacter, boolean breaksOnEOF) :breaksOnEOF表示是否在文件末尾终止该规则。
     ⊙ SingleLineRule(String startSequence, String endSequence, IToken token, char escapeCharacter, boolean breaksOnEOF, boolean escapeContinuesLine) :escapeContinuesLine表示是否在该行结束后使用转义符。
 rules.add(new SingleLineRule("/*", "*/", new Token( comment), '\\'));
表示该规则是在两个字符“/*”和“*/”之间应用,也就是本例程序中注释部分的规则。

● EndOfLineRule:没有终止字符的规则类
EndOfLineRule对象可以设置只有初始字符相同后的规则。
 rules.add(new EndOfLineRule("//", new Token( comment),'\\'));
表示带有“//”标记后的都可以应用此规则。

● WordRule:单词规则
WordRule对象应用于对某些特定的字符设置的规则,一般可以用来设置关键字的规则,当输入的字符串为规则中所指定的一些字符串时,就可以使用该对象。
构造方法如下:
     ⊙ WordRule(IWordDetector detector) :其中IWordDetector为接口,创建WordRule对象时要使用实现这个接口的对象。
     ⊙ WordRule(IWordDetector detector, IToken defaultToken) :也可以设置默认的IToken代码格式对象。
 本程序中对关键字设置的规则使用的就是WordRule规则:
 WordRule keywordRule = new WordRule(new KeywordDetector());
其中KeywordDetector对象是实现了IWordDetector的对象,代码如下:

package www.swt.com.ch21;

import org.eclipse.jface.text.rules.IWordDetector;
public class KeywordDetector implements IWordDetector {

// 接口中的方法,字符是否是单词的开始
public boolean isWordStart(char c) {
//循环所有的关键字
//如果有关键字中的第一个字符匹配该字符,则返回true
for (int i = 0, n = Constants.JS_SYNTAX_KEYWORD.length; i < n; i++)
if (c == ((String) Constants.JS_SYNTAX_KEYWORD[i]).charAt(0))
return true;
return false;
}

// 接口中的方法,字符是否是单词中的一部分
public boolean isWordPart(char c) {
//循环所有的关键字
//如果关键字的字符中有该字符,则返回true
for (int i = 0, n = Constants.JS_SYNTAX_KEYWORD.length; i < n; i++)
if (((String) Constants.JS_SYNTAX_KEYWORD[i]).indexOf(c) != -1)
return true;
return false;
}
}

另外,创建WordRule对象后,还要使用以下方法将该规则所应用的字符添加到该对象中:

void addWord(String word, IToken token)
word为该规则应用的字符串,IToken为规则匹配后所应用的代码样式。例如本程序中循环所有关键字字符,然后添加到对象中:
for (int i = 0, n = Constants.JS_SYNTAX_KEYWORD.length; i < n; i++)
keywordRule.addWord(Constants.JS_SYNTAX_KEYWORD[i], new Token( keywords ));

同理,对JavaScript内置对象也是通过此规则来设定的。代码如下:

package www.swt.com.ch21;

import org.eclipse.jface.text.rules.IWordDetector;
public class ObjectDetector implements IWordDetector {
public boolean isWordStart(char c) {
for (int i = 0, n = Constants.JS_SYNTAX_BUILDIB_OBJECT.length; i < n; i++)
if (c == ((String) Constants.JS_SYNTAX_BUILDIB_OBJECT[i]).charAt(0))
return true;
return false;
}

public boolean isWordPart(char c) {
for (int i = 0, n = Constants.JS_SYNTAX_BUILDIB_OBJECT.length; i < n; i++)
if (((String) Constants.JS_SYNTAX_BUILDIB_OBJECT[i]).indexOf(c) != -1)
return true;
return false;
}
}

疑问:这样的匹配方式是否严格?
举例:如果定义了关键字“abstract”、“super”,输入“auper”时开始字符与“abstract”匹配,后面的部分与“super”匹配,则“auper”是否会被认为是关键字?
答案:不会。
原因:WordRule在最后还会进行全词匹配???

◆ 提取类(Token)和文本属性类(TextAttribute)
IToken为一个接口,实现该接口的类是Token,在规则中使用Token对象可以将符合该规则字符转换成对应Token中设置的文本格式。对于文本格式的设置使用的是TextAttribute类。

TextAttribute类的构造方法:
     ⊙ TextAttribute(Color foreground) :foreground为字符显示的前景色。
     ⊙ TextAttribute(Color foreground, Color background, int style) :background为字符显示背景色,style表示样式常量,SWT.BOLD表示加粗,SWT.ITALIC表示倾斜。

最后,本程序中规则的设置的字体的颜色是从首选项获得的,参考ResourceManager类相关的代码部分。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值