devc代码补全没效果_从零开始写文本编辑器(二十八):自动补全(上)
前言
我本没打算这么早就写“自动补全”功能的。
但是在写XML资源编辑时,为了实现自动引用已有资源@string/xxx,需要一个合适的列表来让我选择。这样能防止拼写错误。
也就是说,初衷是为了防止拼写错误,结果分析了性价比,还是上自动补全功能吧。
XML自动补全是一个较大的模块,它分为多个子模块,本篇发稿时,全部模块还远没完成。在本篇中只讲述自动补全的GUI模块,并演示 java 关键字代码补全作结尾。
调研自动补全 Auto Complete
“自动补全”是一个宽泛的说法,具体到代码编辑器,就是“代码补全”,本篇统称为“自动补全”。
自动补全的好处:
- 自动匹配已知输入字符串,猜测完整字符串
- 简单如:从首字符开始连续匹配
- 高级如:不连续匹配
- 自动弹出选择列表
- 用简单的 UP/DOWN 按键,浏览选择项
- 用简单的 ENTER 键,选择补全项,并自动插入光标位置。
“自动补全”的流程图
这是一张粗糙的流程图,还有小细节,用代码更直观表述。但在上代码之前,先总体说下功能类。
类清单
- Complete:补全。提供“补全”的列表数据
- EatEnter:“吃掉回车”。因为回车符与退出符不同,回车符是可显示字符,当用于确认插入操作时,要主动吃掉。
- Focus:焦点。当显示弹出菜单列表时,要把焦点交还给编辑器,否则无法持续编辑。
- FrameCode:代码窗口。这是我个人习惯用Frame前缀表示某窗口类。
- Insert:插入。完成代码插入,内部记忆了插入的光标位置。
- ListComplete:补全列表。同Frame一样,List前缀表示它是一个JList,是显示补全数据的容器。
- Location:位置。它计算出补全列表弹出的坐标x, y位置,让左上角临近光标。
- PopupMenuComplete:补全弹出菜单。同Frame一样,PopupMenu前缀表示,它是一个JPopupMenu,它是ListComplete的容器,自动处理了 Escape 等逻辑。
- TextEditor:编辑器。这是不是前些篇中的编辑,它没有行号等功能。只是我在“自动补全”中临时编写的。
OK,全部类就是这些了。
用例:弹出菜单显示补全列表
public void keyReleased(java.awt.event.KeyEvent e) {int keyCode = e.getKeyCode();if (keyCode == KeyEvent.VK_UP) {popupMenuComplete.selectPrevious();} else if (keyCode == KeyEvent.VK_DOWN) {popupMenuComplete.selectNext();} else if (keyCode == KeyEvent.VK_ESCAPE) {popupMenuComplete.setVisible(false);popupMenuComplete.clean();} else if (Character.isWhitespace(e.getKeyChar())) {popupMenuComplete.setVisible(false);popupMenuComplete.clean();} else {String headString = textEditor.getHeadString();if (headString != null && headString.length() > 0) {insert.setPosition(textEditor.getCaretPosition());popupMenuComplete.receiveInputString(headString);Point point = location.getLocation();popupMenuComplete.show(textEditor, point.x, point.y);focus.backToTextComponent();}} };当用户输入后,侦听到可见字符输入,编辑器从光标处向前搜索已知输入字符串 headString。
为了方便记忆,我把补全字符串分解为:头部(head string)和 尾部(tail string),这是headString命名的由来。
/*** * @return null if condition failed.*/ public String getHeadString() {int caretPosition = getCaretPosition();String text = getText();int start = indexOfWordStart();if (start > caretPosition) {return null;}return text.substring(start, caretPosition); }比如:输入'p',则返回 "p"。
然后弹出菜单(此时不可见),开始接收已知字符串,进行匹配。
public void receiveInputString(String headString) {this.headString = headString; // String[] data = complete.createListData(headString);Vector<String> data = complete.createListKeywordsJava(headString);listComplete.setListData(data);listComplete.setSelectedIndex(0); }本篇中匹配数据集为 java 关键字,使用简单的起始字符串匹配。
public Vector<String> createListKeywordsJava(String headString) {Vector<String> vector = new Vector<String>();for (String string : keywords) {if (string.startsWith(headString)) {vector.add(string);}}return vector; }private String[] keywords = new String[] { "abstract", "assert", "boolean", "break", "byte", "case", "catch","char", "class", "const", "continue", "default", "do", "double", "else", "enum", "extends", "final","finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long","native", "new", "package", "private", "protected", "public", "return", "strictfp", "short", "static","super", "switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile","while" };返回的字符串向量送给列表显示,并默认选中第1项(index=0)。
返回窗口领空后,把焦点还给编辑器(JTextPane textEditor)。
package editor.xml.visual.autocomplete2;import javax.swing.text.JTextComponent;public class Focus {private JTextComponent textComponent;public Focus(JTextComponent textComponent) {this.textComponent = textComponent;}/*** after show pop up menu, move focus back on text component.*/public void backToTextComponent() {textComponent.requestFocus();} }用例:弹出菜单的位置计算
package editor.xml.visual.autocomplete2;import java.awt.Point; import java.awt.geom.Rectangle2D;import javax.swing.text.BadLocationException; import javax.swing.text.JTextComponent;public class Location {private JTextComponent textComponent;public Location(JTextComponent textComponent) {this.textComponent = textComponent;}/*** * @return point location in text component, not base on screen.*/private Point getCaretLocation() {try {int caretPosition = textComponent.getCaretPosition();Rectangle2D rectangle2d = textComponent.modelToView2D(caretPosition);int x = (int) rectangle2d.getMaxX();int y = (int) rectangle2d.getMaxY();return new Point(x, y);} catch (BadLocationException e2) {e2.printStackTrace();return null;}}/*** <pre>* 提示:invoker 要使用 textComponent为参数* </pre>* * @return point of location left-top on invoker.* */public Point getLocation() {Point point = getCaretLocation();int baseY = textComponent.getBaseline(0, 0);point.y += baseY;return point;}}这段逻辑主要是控件坐标API的使用,invoker 的目标容器会影响显示的父位置,进而会影响总的坐标计算,为了灵活性,我不想写“死“,所以只好加注释说明。
Point point = location.getLocation(); popupMenuComplete.show(textEditor, point.x, point.y);上述的 show(textEditor, ...,如果把 textEditor换成了窗口等父控件,则显示坐标就会计算偏离。
用例:浏览列表
if (keyCode == KeyEvent.VK_UP) {popupMenuComplete.selectPrevious(); } else if (keyCode == KeyEvent.VK_DOWN) {popupMenuComplete.selectNext();// PopupMenuComplete.java public void selectPrevious() {int hopeIndex = listComplete.getSelectedIndex() - 1;int index = Math.max(hopeIndex, 0);listComplete.setSelectedIndex(index); }public void selectNext() {int hopeIndex = listComplete.getSelectedIndex() + 1;int index = Math.max(hopeIndex, 0);listComplete.setSelectedIndex(index); }此处可以写成循环浏览,eclipse 就是可循环浏览,我这里就不复杂化。
用例:取消补全
} else if (keyCode == KeyEvent.VK_ESCAPE) {popupMenuComplete.setVisible(false);popupMenuComplete.clean();用例:插入补全
package editor.xml.visual.autocomplete2;import javax.swing.text.BadLocationException; import javax.swing.text.JTextComponent;public class Insert {public Insert(JTextComponent textComponent) {this.textComponent = textComponent;}private JTextComponent textComponent;private int position;public int getPosition() {return position;}public void setPosition(int position) {this.position = position;}public void insert(String tailString) throws BadLocationException {textComponent.getDocument().insertString(position, tailString, null);} }记得要“吃掉回车符”哦!
package editor.xml.visual.autocomplete2;import javax.swing.text.BadLocationException; import javax.swing.text.JTextComponent;public class EatEnter {private JTextComponent textComponent;public EatEnter(JTextComponent textComponent) {this.textComponent = textComponent;}public void eat() {final int position = textComponent.getCaretPosition();try {textComponent.getDocument().remove(position - 1, 1);} catch (BadLocationException e) {e.printStackTrace();}} }看看效果
在演示中,好像在第二行处有BUG,in的前缀怎么会出现 if 关键字。这些BUG我后续再迭代修正,本篇主要逻辑就是这些。
这是一个非常粗糙的胶水式模块,后续慢慢会演变成一个自动补全框架。
以上~
参考资料
- stackoverflow 搜索 "auto complete" 若干文章
工具
- Gif录制软件:ScreenToGif
总结
以上是生活随笔为你收集整理的devc代码补全没效果_从零开始写文本编辑器(二十八):自动补全(上)的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 在辣妈汇添加收货地址方法图解
- 下一篇: java arm 编译器下载_最全盘点: