本文目录导读:

这是一个非常专业的 Java Swing 编辑器补全问题组合。
针对 JEditorPane + HTMLEditorKit 场景下的 Parser Method Completer(解析器方法补全器),我理解你的需求是:在编辑 HTML 内容时,能够提供针对 HTML 标签、属性、CSS 样式等的代码补全/自动完成功能。
这里没有内置的 "ParserMethodCompleter" 标准类,你提到的这个名称很可能是一个自定义的补全器辅助类,或者是指结合了 HTMLDocument、HTMLDocument.HTMLReader、HTMLEditorKit.Parser、ParserCallback 等 API 实现的补全系统。
为了实现这个目的,通常需要结合 AutoCompletion(自动补全) 机制与 HTML 解析器 来分析当前光标位置的上下文。
下面是一个标准的实现思路和代码骨架,用于在 JEditorPane + HTMLEditorKit 中实现一个“感知 HTML 的补全器” (ParserAwareCompleter):
核心思路
- 监听输入: 使用
DocumentListener或UndoableEditListener监听每一次按键。 - 获取上下文: 当用户输入字符(如
<、="#.`)后,解析当前光标之前的文本。 - 调用解析器: 使用
HTMLEditorKit.Parser对当前文档或文档片段进行解析,或者使用正则表达式/字符串分析来确定当前光标所处的上下文(是否在标签内?是否在属性值内?是否在 CSS 中?)。 - 生成补全列表: 根据上下文返回相应的建议(标签名、属性名、CSS 属性、颜色值等)。
- 显示弹窗: 使用
JPopupMenu或JWindow+JList显示补全建议。 - : 用户选择后,将补全内容插入文档。
示例代码:一个简单的 ParserAwareCompleter 实现
这个示例实现了一个 JEditorPane 在输入 < 后弹出 HTML 标签补全,输入空格后弹出属性补全。
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.event.*;
import javax.swing.text.html.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class HTMLAutoCompleter {
private JEditorPane editorPane;
private JWindow popupWindow;
private JList<String> suggestionList;
private DefaultListModel<String> listModel;
private boolean isActive = false;
// 模拟的标签数据库
private static final String[] HTML_TAGS = {"html", "head", "body", "div", "span", "p", "a", "img", "table", "tr", "td", "h1", "h2", "ul", "li", "input", "form"};
private static final String[] HTML_ATTRS = {"class", "id", "style", "href", "src", "alt", "width", "height", "border", "onclick", "type", "name", "value"};
public HTMLAutoCompleter(JEditorPane editor) {
this.editorPane = editor;
initPopup();
attachListeners();
}
private void initPopup() {
listModel = new DefaultListModel<>();
suggestionList = new JList<>(listModel);
suggestionList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
// 双击或回车选择
suggestionList.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
insertSelectedSuggestion();
}
}
});
popupWindow = new JWindow();
popupWindow.getContentPane().add(new JScrollPane(suggestionList));
popupWindow.setSize(200, 150);
popupWindow.setAlwaysOnTop(true);
// 键盘监听用于在弹窗中导航
editorPane.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (!isActive) return;
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
insertSelectedSuggestion();
e.consume();
} else if (e.getKeyCode() == KeyEvent.VK_UP) {
int idx = suggestionList.getSelectedIndex();
if (idx > 0) suggestionList.setSelectedIndex(idx - 1);
e.consume();
} else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
int idx = suggestionList.getSelectedIndex();
if (idx < listModel.size() - 1) suggestionList.setSelectedIndex(idx + 1);
e.consume();
} else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
hidePopup();
e.consume();
}
}
});
}
private void attachListeners() {
editorPane.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
SwingUtilities.invokeLater(() -> {
try {
int pos = e.getOffset() + e.getLength();
if (pos > 0) {
analyzeAndShowSuggestions(pos);
}
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
@Override
public void removeUpdate(DocumentEvent e) {
hidePopup();
}
@Override
public void changedUpdate(DocumentEvent e) {
// 属性变化忽略
}
});
}
private void analyzeAndShowSuggestions(int caretPosition) {
try {
Document doc = editorPane.getDocument();
String text = doc.getText(0, caretPosition);
// 1. 确定上下文
String prefix = getCurrentPrefix(text);
if (prefix == null || prefix.isEmpty()) {
hidePopup();
return;
}
// 2. 模拟解析器:判断是在标签名位置还是属性位置
// 实际项目应调用 javax.swing.text.html.parser.ParserDelegator 或使用正则
boolean inTag = text.length() > 0 && text.charAt(text.length() - 1 - prefix.length()) == '<';
if (inTag && !text.contains(" ")) {
// 刚输入 <tag
showSuggestions(filterSuggestions(prefix, HTML_TAGS));
} else if (text.matches(".*<[a-zA-Z]+\\s+.*$")) {
// 在标签内部的属性位置
showSuggestions(filterSuggestions(prefix, HTML_ATTRS));
} else {
hidePopup();
}
} catch (BadLocationException e) {
e.printStackTrace();
}
}
// 获取当前光标所在的单词前缀 (从光标往前直到遇到非字母/连字符)
private String getCurrentPrefix(String text) {
StringBuilder prefix = new StringBuilder();
for (int i = text.length() - 1; i >= 0; i--) {
char c = text.charAt(i);
if (Character.isLetterOrDigit(c) || c == '-' || c == ':' || c == '#') {
prefix.insert(0, c);
} else {
break;
}
}
return prefix.toString();
}
private String[] filterSuggestions(String prefix, String[] candidates) {
if (prefix.isEmpty()) return candidates;
return Arrays.stream(candidates)
.filter(s -> s.toLowerCase().startsWith(prefix.toLowerCase()))
.toArray(String[]::new);
}
private void showSuggestions(String[] suggestions) {
listModel.clear();
if (suggestions.length == 0) {
hidePopup();
return;
}
for (String s : suggestions) {
listModel.addElement(s);
}
suggestionList.setSelectedIndex(0);
// 定位弹窗到光标处
try {
Rectangle caretRect = editorPane.modelToView(editorPane.getCaretPosition());
if (caretRect != null) {
Point p = new Point(caretRect.x, caretRect.y + caretRect.height);
SwingUtilities.convertPointToScreen(p, editorPane);
popupWindow.setLocation(p);
popupWindow.setVisible(true);
isActive = true;
}
} catch (BadLocationException e) {
e.printStackTrace();
}
}
private void hidePopup() {
popupWindow.setVisible(false);
isActive = false;
}
private void insertSelectedSuggestion() {
String selected = suggestionList.getSelectedValue();
if (selected == null) return;
try {
// 移除已输入的prefix,插入选择的补全
int caretPos = editorPane.getCaretPosition();
String prefix = getCurrentPrefix(editorPane.getDocument().getText(0, caretPos));
int start = caretPos - prefix.length();
editorPane.getDocument().remove(start, prefix.length());
editorPane.getDocument().insertString(start, selected, null);
} catch (BadLocationException e) {
e.printStackTrace();
}
hidePopup();
}
// 使用示例
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("HTML AutoComplete Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JEditorPane editor = new JEditorPane();
editor.setEditorKit(new HTMLEditorKit());
editor.setText("<html><body><p>Type < to start</p></body></html>");
editor.setCaretPosition(editor.getDocument().getLength());
new HTMLAutoCompleter(editor);
frame.add(new JScrollPane(editor));
frame.setSize(600, 400);
frame.setVisible(true);
});
}
}
"ParserMethodCompleter" 的进阶说明
如果你在项目中看到了一个名为 ParserMethodCompleter 的具体类,它可能是自定义的,其作用通常是:
- 解析 HTML 结构: 利用
HTMLEditorKit.ParserCallback跟踪当前文档的标签栈,以确定补全上下文(在<table>内补全<tr>的可能性更高)。 - 方法补全: 对于特定的属性或事件处理器(如
onclick、style),提供 JavaScript 函数名或 CSS 属性名的补全。 - 性能优化: 缓存解析结果,避免每次按键都重新解析整个文档。
你可以参考上述代码的基础,进一步实现一个 ParserAwareCompleter 类,该类通过组合 HTMLEditorKit.Parser 来实现更智能的上下文感知补全。