我正在使用 Swing 制作一个简单的 markdown 文本编辑器,它可以在其主 JTextPane 中实时呈现 markdown 格式。也就是说,markdown 格式的文本*hello*
一旦检测到就会以斜体显示,但会以纯文本形式保存。
为此,我想出了一个正则表达式来获取降价标记(让我们现在仅用*italics*
作示例),每隔几秒钟我的代码就会搜索 JTextPane 的文本并使用 JTextPane#setCharacterAttributes 来更改相关区域的格式.
// init
PLAIN = Document.addStyle("plain", null);
StyleConstants.setFontSize(PLAIN, 12);
ITALIC = Document.addStyle("italic", null);
StyleConstants.setItalic(ITALIC, true);
...
// every few seconds
// remove all formatting
Document.setCharacterAttributes(0, Document.getLength(), PLAIN, true);
// italicize parts that the regex matches
m = Pattern.compile("\\*([^\\n*]+)\\*").matcher(temp);
while (m.find()) {
Document.setCharacterAttributes(m.start(), m.group().length(), ITALIC, false);
}
问题是“活跃度”——一段时间后,JTextPane 开始用characters 而不是 words换行,有时会完全失去自动换行,只显示未换行的行。
有什么办法可以修复这个/扩展 JTextPane 来修复它,还是 JTextPane 根本不适合这种实时更新?我用谷歌搜索了很长时间,但找不到任何东西;我只是不确定要搜索什么。
package test;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.lang.reflect.Field;
import java.util.logging.*;
import java.util.regex.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
public class Test {
private JFrame frame = new JFrame();
private JTextPane jtp;
private StyledDocument doc;
// NEW LINES
private Timer T;
private boolean update = true;
MarkdownRenderer m;
// END OF NEW LINES
public Test() {
jtp = new JTextPane();
jtp.setEditorKit(new MyStyledEditorKit());
jtp.setText("\ntype some text in the above empty line and check the wrapping behavior");
doc = jtp.getStyledDocument();
// NEW LINES
m = new MarkdownRenderer(jtp);
Timer T = new Timer(2000, new java.awt.event.ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (update) {
update = false;
m.render();
}
}
});
T.start();
// END OF NEW LINES
doc.addDocumentListener(new DocumentListener() {
private boolean doUpdate = true;
public void insertUpdate(DocumentEvent e) {
insert();
}
public void removeUpdate(DocumentEvent e) {
insert();
}
public void changedUpdate(DocumentEvent e) {
// triggers every time formatting is changed
// insert();
}
public void insert() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Style defaultStyle = jtp.getStyle(StyleContext.DEFAULT_STYLE);
doc.setCharacterAttributes(0, doc.getLength(), defaultStyle, false);
update = true;
}
});
}
});
JScrollPane scroll = new JScrollPane(jtp);
scroll.setPreferredSize(new Dimension(200, 200));
frame.add(scroll);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Test bugWrapJava7 = new Test();
}
});
}
}
class MyStyledEditorKit extends StyledEditorKit {
private MyFactory factory;
public ViewFactory getViewFactory() {
if (factory == null) {
factory = new MyFactory();
}
return factory;
}
}
class MyFactory implements ViewFactory {
public View create(Element elem) {
String kind = elem.getName();
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new MyLabelView(elem);
} else if (kind.equals(AbstractDocument.ParagraphElementName)) {
return new ParagraphView(elem);
} else if (kind.equals(AbstractDocument.SectionElementName)) {
return new BoxView(elem, View.Y_AXIS);
} else if (kind.equals(StyleConstants.ComponentElementName)) {
return new ComponentView(elem);
} else if (kind.equals(StyleConstants.IconElementName)) {
return new IconView(elem);
}
}
// default to text display
return new LabelView(elem);
}
}
class MyLabelView extends LabelView {
public MyLabelView(Element elem) {
super(elem);
}
public View breakView(int axis, int p0, float pos, float len) {
if (axis == View.X_AXIS) {
resetBreakSpots();
}
return super.breakView(axis, p0, pos, len);
}
private void resetBreakSpots() {
try {
// HACK the breakSpots private fields
Field f=GlyphView.class.getDeclaredField("breakSpots");
f.setAccessible(true);
f.set(this, null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MarkdownRenderer {
private static final MutableAttributeSet PLAIN = new SimpleAttributeSet();
private static final MutableAttributeSet BOLD = new SimpleAttributeSet();
private static final MutableAttributeSet ITALIC = new SimpleAttributeSet();
private static final MutableAttributeSet UNDERLINE = new SimpleAttributeSet();
private StyledDocument Document = null;
public MarkdownRenderer(JTextPane editor) {
Document = (StyledDocument) editor.getDocument();
StyleConstants.setBold(BOLD, true);
StyleConstants.setItalic(ITALIC, true);
StyleConstants.setUnderline(UNDERLINE, true);
}
void render() {
String s = "";
try {
s = Document.getText(0, Document.getLength());
} catch (BadLocationException ex) {
Logger.getLogger(MarkdownRenderer.class.getName()).log(Level.SEVERE, null, ex);
}
// Document.setCharacterAttributes(0, Document.getLength(), PLAIN, true);
String temp = s.replaceAll("\\*\\*([^\\n*]+)\\*\\*", "|`$1`|"); // can also use lazy quantifier: (.+?)
Matcher m = Pattern.compile("\\|`.+?`\\|").matcher(temp);
while (m.find()) {
Document.setCharacterAttributes(m.start(), m.group().length(), BOLD, false);
}
m = Pattern.compile("\\*([^\\n*]+)\\*").matcher(temp);
while (m.find()) {
Document.setCharacterAttributes(m.start(), m.group().length(), ITALIC, false);
}
m = Pattern.compile("_+([^\\n*]+)_+").matcher(temp);
while (m.find()) {
Document.setCharacterAttributes(m.start(), m.group().length(), UNDERLINE, false);
}
}
}