JEditorPaneHTMLEditorKitParserMethodCompleter补全器

wen java案例 1

本文目录导读:

JEditorPaneHTMLEditorKitParserMethodCompleter补全器

  1. 核心思路
  2. 示例代码:一个简单的 ParserAwareCompleter 实现
  3. 关于 "ParserMethodCompleter" 的进阶说明

这是一个非常专业的 Java Swing 编辑器补全问题组合。

针对 JEditorPane + HTMLEditorKit 场景下的 Parser Method Completer(解析器方法补全器),我理解你的需求是:在编辑 HTML 内容时,能够提供针对 HTML 标签、属性、CSS 样式等的代码补全/自动完成功能。

这里没有内置的 "ParserMethodCompleter" 标准类,你提到的这个名称很可能是一个自定义的补全器辅助类,或者是指结合了 HTMLDocumentHTMLDocument.HTMLReaderHTMLEditorKit.ParserParserCallback 等 API 实现的补全系统。

为了实现这个目的,通常需要结合 AutoCompletion(自动补全) 机制与 HTML 解析器 来分析当前光标位置的上下文。

下面是一个标准的实现思路和代码骨架,用于在 JEditorPane + HTMLEditorKit 中实现一个“感知 HTML 的补全器” (ParserAwareCompleter):


核心思路

  1. 监听输入: 使用 DocumentListenerUndoableEditListener 监听每一次按键。
  2. 获取上下文: 当用户输入字符(如 <、="#.`)后,解析当前光标之前的文本。
  3. 调用解析器: 使用 HTMLEditorKit.Parser 对当前文档或文档片段进行解析,或者使用正则表达式/字符串分析来确定当前光标所处的上下文(是否在标签内?是否在属性值内?是否在 CSS 中?)。
  4. 生成补全列表: 根据上下文返回相应的建议(标签名、属性名、CSS 属性、颜色值等)。
  5. 显示弹窗: 使用 JPopupMenuJWindow + JList 显示补全建议。
  6. : 用户选择后,将补全内容插入文档。

示例代码:一个简单的 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 的具体类,它可能是自定义的,其作用通常是:

  1. 解析 HTML 结构: 利用 HTMLEditorKit.ParserCallback 跟踪当前文档的标签栈,以确定补全上下文(在 <table> 内补全 <tr> 的可能性更高)。
  2. 方法补全: 对于特定的属性或事件处理器(如 onclickstyle),提供 JavaScript 函数名或 CSS 属性名的补全。
  3. 性能优化: 缓存解析结果,避免每次按键都重新解析整个文档。

你可以参考上述代码的基础,进一步实现一个 ParserAwareCompleter 类,该类通过组合 HTMLEditorKit.Parser 来实现更智能的上下文感知补全。

抱歉,评论功能暂时关闭!