简介
Swing提供了JTextArea, JTextPane等组件提供编辑器功能,IntelliJ也提供了一个编辑器组件,提供了许多高级的特性: 语法高亮、代码提示、折叠等功能。Editors除了可以展示在editor tab页中外,还可以被内嵌到dialogs, tool windows中。
使用方法
基本使用
IntelliJ提供了EditorTextField
类来实现Editor功能,可以指定以下特性:
- 文件类型
- 文件是否只读
- 文件是单行或者多行模式
EditorTextField textField = new EditorTextField(content, project, JavaFileType.INSTANCE);
textField.setOneLineMode(false);
textField.setViewer(true);
需要注意的是,上面代码使用的JavaFileType.INSTANCE类,并不在底层API中,它依赖于特定的平台:java。你可以手动新建这个类在自己的插件项目中,或者给你的插件增加java的依赖(增加java依赖后,你的插件将无法安装到intelliJ平台的其他软件中)
# build.gradle.kts
intellij {
plugins.set(listOf("com.intellij.java"))
}
# plugin.xml
<depends>com.intellij.java</depends>
水平滚动条
除了基本使用可以指定的特性外,其他特性你都无法直接设置,需要继承EditorTextField类,重写createEditor方法。这里推荐将自定义的部分封装成类,实现EditorCustomization接口。IntelliJ内置了一些EditorCustomization的实现,如:
- SpellCheckingEditorCustomization: 禁用拼写检查
- HorizontalScrollBarEditorCustomization: 开启或关闭水平滚动条
- ErrorStripeEditorCustomization: 打开或关闭编辑器最右侧的错误条纹提示
以下是增加水平滚动条的示例:
public class HorizontalScrollBarEditor extends EditorTextField {
public HorizontalScrollBarEditor(Project project, FileType fileType) {
super(project, fileType);
}
@Override
protected @NotNull EditorEx createEditor() {
EditorEx editor = super.createEditor();
HorizontalScrollBarEditorCustomization.ENABLED.customize(editor);
return editor;
}
如果你直接在EditorTextField 外部套一个JScrollBar,水平滚动条会挡住编辑器底部的内容。除了继承EditorTextField类进行定制化外,IntelliJ还内置了许多子类,如果你想在一个dialog中放置一个editor来接受用户的额输入,使用LanguageTextField会更合适。
写入文本
Editor使用了读写锁,写入文本时需要获取写锁。
WriteCommandAction.runWriteCommandAction(project, () -> {
Document document = textField.getDocument();
document.insertString(document.getTextLength(), stringToAppend);
});
带补全功能的编辑器
当你在自己Dialog或者ToolWindow等内嵌EditorTextField的场景,需要实现补全功能时,可以使用TextFieldWithCompletion,并实现TextFieldCompletionProvider接口。
EditorTextField input = new TextFieldWithCompletion(project, new MyTextFieldCompletionProvider(), ""
, false, true, false);
input.addSettingsProvider(editorEx -> {
// 如果你不需要"/"作为提示符,则不需要添加DocumentListener
Disposable inputDisposable = Disposer.newDisposable("inputListener");
EditorUtil.disposeWithEditor(editorEx, inputDisposable);
editorEx.getDocument().addDocumentListener(new InputDocumentListener(editorEx), inputDisposable);
editorEx.setBorder(null);
});
// Idea Completion对于"/"字符直接忽略,如果你需要输入"/"的时候就唤醒提示,需要手动调用scheduleAutoPopup方法
@RequiredArgsConstructor
static class InputDocumentListener implements DocumentListener {
@NotNull
private final Editor editor;
@Override
public void documentChanged(@NotNull DocumentEvent event) {
String text = event.getDocument().getText();
if (text.equals("/")) {
AutoPopupController.getInstance(Objects.requireNonNull(editor.getProject())).scheduleAutoPopup(editor);
}
}
}
static class MyTextFieldCompletionProvider extends TextFieldCompletionProvider {
@Override
protected void addCompletionVariants(@NotNull String text, int offset, @NotNull String prefix
, @NotNull CompletionResultSet result) {
// 添加补全项
LookupElementBuilder elementBuilder = LookupElementBuilder.create("/clear");
result.addElement(elementBuilder);
}
}
工具方法
// 获取Editor对应的PsiFile
PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
// 往Editor存入用户自定义数据
Key<String> myKey = Key.create("myKey")
//Key<Boolean> myKeyWithDefaultValue = Key.create("myKey", Boolean.FALSE);
myKey.set(editor, "myValue");
String myValue = myKey.get(editor);
// 判断当前补全popup是否处于打开状态
CompletionServiceImpl.getCurrentCompletionProgressIndicator() != null
// 关闭popup的提示栏
editor.putUserData(AutoPopupController.NO_ADS, true);
注意事项
- 设置Empty Border必须用IntelliJ的JBEmptyBorder,否则在部分版本下容易出现空文本下光标看不到的问题。editor.setBorder(JBUI.Borders.empty(JBUI.insets(5)));
- 部分ide版本在行高发生变化后,页面渲染不太及时,需要自己调用repaint刷新
Disposable inputDisposable = Disposer.newDisposable("inputListener");
EditorUtil.disposeWithEditor(editor, inputDisposable);
editor.getDocument().addDocumentListener(new MyDocumentListener(editor), inputDisposable);
@RequiredArgsConstructor
static class InputDocumentListener implements DocumentListener {
@NotNull
private final EditorEx editor;
private int lastLineCount = 0;
@Override
public void documentChanged(@NotNull DocumentEvent event) {
Document document = event.getDocument();
int newLineCount = document.getLineCount();
if (lastLineCount != newLineCount) {
lastLineCount = newLineCount;
editor.getComponent().revalidate();
editor.getComponent().repaint();
}
}
}