StyledText实现的简单Java类文件编辑器
因为所处行业缘故,实施拷贝的虚拟机中都是只有环境,没有源码,包括Java的和TC的,所有的东西都没有源码,只能自己装反编译插件查看源码,但我不知道为啥,这个反编译插件反编译出来的代码前面都有空注释,中间穿插大量的空白行,最后还有反编译信息。反编译信息倒无所谓,只是代码开头的空注释和中间的空白行很让人眼花,很讨厌。某天心血来潮,自己写一个编辑器,处理一下这些代码。于是开动了。
其实这个东西写完后自己发现,基本毫无用处,真正开发过程中看源代码时需要了解各个方法相互怎么调用的,还会通过代码大纲总体预览这个类什么的。而自己实现的这个编辑器,除了看起来稍微好看一点,基本没啥用处。不过没关系,就当学习练手了吧。
一开始就是想直接用SWT的Text行了,后来想起StyleText,看名字感觉更高端一些,应该能实现很多炫酷的功能,就决定用StyleText了,然后深入研究发现,这两个玩意根本不是一回事。先看下继承关系。
是的,Text是Scrollable的直接子类,而StyledText是Canvas的直接子类,这意味着什么,整个StyledText就是用Canvas画出来的,感觉也是很牛逼啊。想想也是,不然Eclipse中的代码着色等等高级功能如何实现?单靠Text是无法完成的,功能太单一了。
用上StyledText先实现我最基本的功能吧,就是简单的找到字符串中的前8列字符,中间的空白字符,和最后7行代码。把他们清除掉。实现完成后在网上搜索StyledText,发现还可以实现更多有意思的功能,内容助理,代码着色等,参考别人博客的代码和自己看源码,慢慢实现出了这几个功能,下面贴出效果图。
A. 这是从反编译器中粘贴过来的代码,很乱。
B. 经过解析后,去除了注释和空白行,并对关键字进行着色。
C.再次输入代码时,对相关内容会弹出内容助理的窗口
D.如果输入的单词是关键字,会进行着色。
好了,运行效果就是这样,下面贴出代码,相关的东西都已经在代码里详细注释了。
主类:
1 import java.io.File; 2 import java.io.FileWriter; 3 import java.io.IOException; 4 import java.util.ArrayList; 5 import java.util.List; 6 import java.util.regex.Matcher; 7 import java.util.regex.Pattern; 8 9 import org.eclipse.jface.dialogs.MessageDialog; 10 import org.eclipse.jface.text.Document; 11 import org.eclipse.jface.text.IDocument; 12 import org.eclipse.jface.text.ITextSelection; 13 import org.eclipse.jface.text.contentassist.ContentAssistant; 14 import org.eclipse.jface.text.contentassist.IContentAssistant; 15 import org.eclipse.jface.text.source.ISourceViewer; 16 import org.eclipse.jface.text.source.SourceViewer; 17 import org.eclipse.jface.text.source.SourceViewerConfiguration; 18 import org.eclipse.swt.SWT; 19 import org.eclipse.swt.custom.StyleRange; 20 import org.eclipse.swt.custom.StyledText; 21 import org.eclipse.swt.custom.VerifyKeyListener; 22 import org.eclipse.swt.events.ModifyEvent; 23 import org.eclipse.swt.events.ModifyListener; 24 import org.eclipse.swt.events.SelectionAdapter; 25 import org.eclipse.swt.events.SelectionEvent; 26 import org.eclipse.swt.events.VerifyEvent; 27 import org.eclipse.swt.layout.FillLayout; 28 import org.eclipse.swt.widgets.Button; 29 import org.eclipse.swt.widgets.Composite; 30 import org.eclipse.swt.widgets.Control; 31 import org.eclipse.swt.widgets.Display; 32 import org.eclipse.swt.widgets.FileDialog; 33 import org.eclipse.swt.widgets.Shell; 34 import org.eclipse.wb.swt.SWTResourceManager; 35 36 import swing2swt.layout.BorderLayout; 37 38 public class ClassEditor { 39 40 protected Shell shell; 41 private SourceViewer sourceViewer; 42 private List<String> keywords = new ArrayList<String>(); 43 private StyledText styledText; 44 //匹配Java关键字的正则表达式 45 public static final String REG= "\\b(abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|extends|false|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|native|new|null|package|private|protected|public|return|short|static|strictfp|super|switch|synchronized|this|throw|throws|true|transient|try|void|volatile|while)\\b"; 46 47 public static void main(String[] args) { 48 try { 49 ClassEditor window = new ClassEditor(); 50 window.open(); 51 } catch (Exception e) { 52 e.printStackTrace(); 53 } 54 } 55 56 /** 57 * Open the window. 58 */ 59 public void open() { 60 Display display = Display.getDefault(); 61 createContents(); 62 shell.open(); 63 shell.layout(); 64 while (!shell.isDisposed()) { 65 if (!display.readAndDispatch()) { 66 display.sleep(); 67 } 68 } 69 } 70 71 /** 72 * Create contents of the window. 73 */ 74 protected void createContents() { 75 shell = new Shell(); 76 shell.setSize(612, 468); 77 shell.setText("类文件编辑器"); 78 shell.setLayout(new BorderLayout(0, 0)); 79 80 sourceViewer = new SourceViewer(shell, null, SWT.V_SCROLL); 81 //此处的Document非必备参数,但是当需要实现内容助手等复杂功能时,必须添加此参数,否则会一直报空指针异常 82 //本人就是一直报异常,查看源码才找出这个问题的。内容助手底层代码使用到了这个Document对象。 83 sourceViewer.setDocument(new Document()); 84 styledText = sourceViewer.getTextWidget(); 85 styledText.setText("/* */ package org.eclipse.jface.dialogs;\r\n/* */ \r\n/* */ import org.eclipse.swt.custom.StackLayout;\r\n/* */ import org.eclipse.swt.widgets.Composite;\r\n/* */ import org.eclipse.swt.widgets.ProgressBar;\r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ public class ProgressIndicator\r\n/* */ extends Composite\r\n/* */ {\r\n/* */ private static final int PROGRESS_MAX = 1000;\r\n/* 30 */ private boolean animated = true;\r\n/* */ \r\n/* */ \r\n/* */ private StackLayout layout;\r\n/* */ \r\n/* */ \r\n/* */ private ProgressBar determinateProgressBar;\r\n/* */ \r\n/* */ \r\n/* */ private ProgressBar indeterminateProgressBar;\r\n/* */ \r\n/* */ \r\n/* */ private double totalWork;\r\n/* */ \r\n/* */ private double sumWorked;\r\n/* */ \r\n/* */ \r\n/* */ public ProgressIndicator(Composite parent)\r\n/* */ {\r\n/* 49 */ this(parent, 0);\r\n/* */ }\r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ public ProgressIndicator(Composite parent, int style)\r\n/* */ {\r\n/* 62 */ super(parent, 0);\r\n/* */ \r\n/* */ \r\n/* 65 */ if ((style & 0x200) == 0) {\r\n/* 66 */ style |= 0x100;\r\n/* */ }\r\n/* 68 */ this.determinateProgressBar = new ProgressBar(this, style);\r\n/* 69 */ this.indeterminateProgressBar = new ProgressBar(this, style | \r\n/* 70 */ 0x2);\r\n/* 71 */ this.layout = new StackLayout();\r\n/* 72 */ setLayout(this.layout);\r\n/* */ }\r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ public void beginAnimatedTask()\r\n/* */ {\r\n/* 79 */ done();\r\n/* 80 */ this.layout.topControl = this.indeterminateProgressBar;\r\n/* 81 */ layout();\r\n/* 82 */ this.animated = true;\r\n/* */ }\r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ public void beginTask(int max)\r\n/* */ {\r\n/* 92 */ done();\r\n/* 93 */ this.totalWork = max;\r\n/* 94 */ this.sumWorked = 0.0D;\r\n/* 95 */ this.determinateProgressBar.setMinimum(0);\r\n/* 96 */ this.determinateProgressBar.setMaximum(1000);\r\n/* 97 */ this.determinateProgressBar.setSelection(0);\r\n/* 98 */ this.layout.topControl = this.determinateProgressBar;\r\n/* 99 */ layout();\r\n/* 100 */ this.animated = false;\r\n/* */ }\r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ public void done()\r\n/* */ {\r\n/* 107 */ if (!this.animated) {\r\n/* 108 */ this.determinateProgressBar.setMinimum(0);\r\n/* 109 */ this.determinateProgressBar.setMaximum(0);\r\n/* 110 */ this.determinateProgressBar.setSelection(0);\r\n/* */ }\r\n/* 112 */ this.layout.topControl = null;\r\n/* 113 */ layout();\r\n/* */ }\r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ public void sendRemainingWork()\r\n/* */ {\r\n/* 120 */ worked(this.totalWork - this.sumWorked);\r\n/* */ }\r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ public void worked(double work)\r\n/* */ {\r\n/* 128 */ if ((work == 0.0D) || (this.animated)) {\r\n/* 129 */ return;\r\n/* */ }\r\n/* 131 */ this.sumWorked += work;\r\n/* 132 */ if (this.sumWorked > this.totalWork) {\r\n/* 133 */ this.sumWorked = this.totalWork;\r\n/* */ }\r\n/* 135 */ if (this.sumWorked < 0.0D) {\r\n/* 136 */ this.sumWorked = 0.0D;\r\n/* */ }\r\n/* 138 */ int value = (int)(this.sumWorked / this.totalWork * 1000.0D);\r\n/* 139 */ if (this.determinateProgressBar.getSelection() < value) {\r\n/* 140 */ this.determinateProgressBar.setSelection(value);\r\n/* */ }\r\n/* */ }\r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ public void showError()\r\n/* */ {\r\n/* 149 */ this.determinateProgressBar.setState(1);\r\n/* 150 */ this.indeterminateProgressBar.setState(1);\r\n/* */ }\r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ public void showPaused()\r\n/* */ {\r\n/* 158 */ this.determinateProgressBar.setState(4);\r\n/* 159 */ this.indeterminateProgressBar.setState(4);\r\n/* */ }\r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ \r\n/* */ public void showNormal()\r\n/* */ {\r\n/* 167 */ this.determinateProgressBar.setState(0);\r\n/* 168 */ this.indeterminateProgressBar.setState(0);\r\n/* */ }\r\n/* */ }\r\n\r\n/* Location: D:\\Siemens\\Teamcenter8\\portal\\plugins\\org.eclipse.jface_3.5.0.I20090525-2000.jar\r\n * Qualified Name: org.eclipse.jface.dialogs.ProgressIndicator\r\n * Java Class Version: 1.2 (46.0)\r\n * JD-Core Version: 0.7.0.1\r\n */"); 86 styledText.setFont(SWTResourceManager.getFont("微软雅黑", 12, SWT.NORMAL)); 87 styledText.setAlwaysShowScrollBars(false); 88 styledText.setSelectionBackground(SWTResourceManager.getColor(SWT.COLOR_BLACK)); 89 styledText.setBackground(SWTResourceManager.getColor(199, 237, 204)); 90 /** 91 * 1.文本修改监听,只有当文本框内的内容被修改时执行的操作,添加或删除文字时会执行的操作 92 * 其余的比如按下Shift键等操作不会被监听到,此监听应该和下面的那个键盘监听区分开来 93 * 2.这个监听中实现了输入Java关键字后自动着色功能 94 * 3.实现思路 95 * a,从SourceViewer对象中获取ITextSelection对象(所有Viewer中基本都有此对象) 96 * b,获取当前光标所处的位置,然后根据此位置获取当前输入的单词的起始位置 97 * c,计算当前输入的单词的长度并判断此单词是否是Java关键字,是就进行着色。 98 */ 99 styledText.addModifyListener(new ModifyListener() { 100 public void modifyText(ModifyEvent e) { 101 ITextSelection selection = (ITextSelection) sourceViewer.getSelectionProvider().getSelection(); 102 int offset = selection.getOffset(); 103 String key = MyContentAssist.extractPrefix(sourceViewer,offset); 104 int p = offset-key.length(); 105 if(key.length()>0&&keywords.contains(key)) { 106 styledText.setStyleRange(new StyleRange(p, key.length(), SWTResourceManager.getColor(SWT.COLOR_DARK_MAGENTA), null)); 107 }else { 108 styledText.setStyleRange(new StyleRange(p, key.length(), null, null)); 109 } 110 } 111 }); 112 113 114 Composite composite = new Composite(shell, SWT.NONE); 115 composite.setLayoutData(BorderLayout.SOUTH); 116 composite.setLayout(new FillLayout(SWT.HORIZONTAL)); 117 118 Button btnParse = new Button(composite, SWT.NONE); 119 btnParse.setText("解析"); 120 btnParse.addSelectionListener(new SelectionAdapter() { 121 @Override 122 public void widgetSelected(SelectionEvent e) { 123 /** 124 * 功能:删除所有的注释和空白行 125 * 思路:用换行符将整个文本分成数组,然后将每一行的前8个去掉,并将最后7行删除,最后拼接起来 126 * 缺点:太死板 127 */ 128 String string = styledText.getText(); 129 String[] split = string.split("\r\n"); 130 StringBuilder result = new StringBuilder(); 131 for(int i=0;i<split.length-7;i++) { 132 String s = split[i].substring(9); 133 if(s.equals(" ")) { 134 continue; 135 }else { 136 result = result.append(s).append("\r\n"); 137 } 138 } 139 styledText.setText(result.toString()); 140 btnParse.setEnabled(false); 141 /** 142 * 另一种实现方式,用正则表达式匹配所有的注释,并删除 143 * 但是此方法还没删除空白行 144 */ 145 // Pattern pattern = Pattern.compile("/\\*{1,2}[\\S\\s]*?\\*/"); 146 // Matcher matcher = pattern.matcher(string); 147 // styledText.setText(matcher.replaceAll("")); 148 parseKey(); 149 } 150 /** 151 * 此方法主要利用正则表达式为整个文本框中的代码中的关键字和引号中的字符串着色 152 */ 153 private void parseKey() { 154 String text = sourceViewer.getTextWidget().getText(); 155 Pattern pattern = Pattern.compile(REG); 156 Matcher matcher = pattern.matcher(text); 157 while(matcher.find()) { 158 int start = matcher.start(); 159 int end = matcher.end(); 160 styledText.setStyleRange(new StyleRange(start, end-start, SWTResourceManager.getColor(SWT.COLOR_DARK_MAGENTA), null)); 161 } 162 Pattern pattern2 = Pattern.compile("\".*\""); 163 Matcher matcher2 = pattern2.matcher(text); 164 while(matcher2.find()) { 165 int start = matcher2.start(); 166 int end = matcher2.end(); 167 styledText.setStyleRange(new StyleRange(start, end-start, SWTResourceManager.getColor(SWT.COLOR_BLUE), null)); 168 } 169 } 170 }); 171 172 Button btnExport = new Button(composite, SWT.NONE); 173 btnExport.setText("导出"); 174 btnExport.addSelectionListener(new SelectionAdapter() { 175 @Override 176 public void widgetSelected(SelectionEvent e) { 177 FileDialog dialog = new FileDialog(shell,SWT.SAVE); 178 dialog.setFilterExtensions(new String[] {"*.java"}); 179 dialog.setText("保存为..."); 180 String path = dialog.open(); 181 if(null==path) return; 182 File file = new File(path); 183 try { 184 if(!file.exists()) { 185 file.createNewFile(); 186 } 187 FileWriter writer = new FileWriter(file); 188 writer.write(styledText.getText()); 189 writer.close(); 190 MessageDialog.openInformation(shell, "导出成功!", "文本已成功导出至"+path+"目录下!"); 191 } catch (IOException e1) { 192 e1.printStackTrace(); 193 } 194 } 195 }); 196 197 Button btnExit = new Button(composite, SWT.NONE); 198 btnExit.setText("关闭"); 199 composite.setTabList(new Control[]{btnParse, btnExport, btnExit}); 200 btnExit.addSelectionListener(new SelectionAdapter() { 201 @Override 202 public void widgetSelected(SelectionEvent e) { 203 shell.dispose(); 204 } 205 }); 206 initData(); 207 } 208 209 private void initData() { 210 keywords.add("private"); 211 keywords.add("void"); 212 keywords.add("new"); 213 keywords.add("this"); 214 keywords.add("return"); 215 keywords.add("class"); 216 keywords.add("if"); 217 218 /** 219 * 配置内容助理 220 */ 221 sourceViewer.configure(new SourceViewerConfiguration() { 222 @Override 223 public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) { 224 ContentAssistant contentAssistant = new ContentAssistant(); 225 contentAssistant.setInformationControlCreator(getInformationControlCreator(sourceViewer)); 226 contentAssistant.enableAutoActivation(true); 227 contentAssistant.setContentAssistProcessor(new MyContentAssist(), IDocument.DEFAULT_CONTENT_TYPE); 228 return contentAssistant; 229 } 230 }); 231 /** 232 * 添加键盘监听,输入“Alt+/”时,打开内容助理 233 */ 234 styledText.addVerifyKeyListener(new VerifyKeyListener() { 235 public void verifyKey(VerifyEvent event) { 236 if(event.stateMask == SWT.ALT && event.character =='/') { 237 if(sourceViewer.canDoOperation(SourceViewer.CONTENTASSIST_PROPOSALS)) { 238 sourceViewer.doOperation(SourceViewer.CONTENTASSIST_PROPOSALS); 239 event.doit = false; 240 } 241 } 242 } 243 }); 244 } 245 }
内容助理类:
import java.util.ArrayList; import java.util.List; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.contentassist.CompletionProposal; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.IContentAssistProcessor; import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.jface.text.contentassist.IContextInformationValidator; public class MyContentAssist implements IContentAssistProcessor { @Override public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { String[] keywords = new String[] {"continue","public","return","reta"}; ITextSelection selection= (ITextSelection) viewer.getSelectionProvider().getSelection(); if (selection.getOffset() == offset) { offset= selection.getOffset() + selection.getLength(); } String text= extractPrefix(viewer, offset); List<String> list = new ArrayList<String>(); for(String s:keywords) { if(text.length()<s.length()&&s.substring(0, text.length()).equalsIgnoreCase(text)) { list.add(s); } } if(list.size()>0) { CompletionProposal[] resu = new CompletionProposal[list.size()]; for(int i=0;i<list.size();i++) { resu[i] = new CompletionProposal(list.get(i), offset-text.length(), text.length(), list.get(i).length()); } return resu; }else { return new CompletionProposal[0]; } } /** * 此方法是从源码中提取出来的,主要是提取当前光标所处位置到前面非Java字符的字符串 * @param viewer * @param offset * @return */ public static String extractPrefix(ITextViewer viewer, int offset) { int i= offset; IDocument document= viewer.getDocument(); if (i > document.getLength()) return ""; try { while (i > 0) { char ch= document.getChar(i - 1); if (!Character.isJavaIdentifierPart(ch)) break; i--; } return document.get(i, offset - i); } catch (BadLocationException e) { return ""; } } @Override public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) { return null; } @Override public char[] getCompletionProposalAutoActivationCharacters() { return null; } @Override public char[] getContextInformationAutoActivationCharacters() { return null; } @Override public String getErrorMessage() { return null; } @Override public IContextInformationValidator getContextInformationValidator() { return null; } }
要运行的话直接把这两个类放到一个包里运行主类就可以了。