创建一个HTML编辑器
内容助手的概念与JFace文本查看器的特定实现有关,该实现是org.eclipse.jface.text.source.SourceViewer
类。 整个Eclipse工作台都使用此类的实例来实现各种编辑器。 但是, SourceViewers
不限于Eclipse工作台,而是可以在基于SWT和JFace JAR构建的任何应用程序中使用。 本文演示了在Eclipse编辑器插件的上下文中内容助手的实现,并提供了有关如何将内容助手与“裸” SourceViewers
一起使用的一些提示。
让我们实现一个简单HTML编辑器。 在这里,内容助手会非常有帮助。 例如,内容助手可以生成典型HTML结构,例如表格或链接,或者可以将选定的文本区域包装到样式标签中。
为了节省时间,我们将使用“ 新建插件项目”向导之一来生成适当的编辑器插件,以实现此编辑器。 因为此生成的编辑器是XML编辑器,而HTML是基于XML的标记语言,所以我们只需进行一些较小的修改即可将该生成的编辑器转换为HTML编辑器。 让我们开始。
调用“ 新建”向导后,选择“插件开发”和“插件项目”。 在以下屏幕上,输入项目名称“ Sample HTML Editor”。 在下一个屏幕中,定义合适的插件ID,例如“ com.bdaum.SampleHTMLEditor”。 以下屏幕允许您选择适当的代码生成向导。 选择带有编辑器的插件 ,如图1所示。
图1.带有编辑器的插件
在下一个屏幕上,修改建议的插件名称(如果需要)和插件类名称,并指定提供程序名称。 一切都保持原样。
继续到下一个屏幕,并将建议的“ 编辑器类名 ”修改为“ HTMLEditor”,将“ 编辑器名 ”修改为“ Sample HTML Editor”,并将文件扩展名修改为“ html,htm”,如图2所示。后一个条目将关联新的编辑器,其中所有文件的文件扩展名为.html或.htm。
图2.编辑器选项

单击完成按钮以生成新的编辑器。 现在,通过运行>运行方式...>运行时工作台启动新的工作台 。 在创建带有.htm或.html文件扩展名的新文件(或导入此类文件)之后,请使用新的编辑器将其打开。
添加内容助手
您很快就会发现,该编辑器没有内容助手。 按Ctrl +空格键无效。 默认情况下, SourceViewers
不配备内容助手。 我们需要适当地配置HTML编辑器中使用的SourceViewer
。
HTML编辑器的SourceViewer
的配置由生成的类XMLConfiguration
表示,该类是SourceViewerConfiguration
的子类(如果您愿意,可以将此类重命名为HTMLConfiguration
,但这不是必需的)。 要将内容助手添加到源查看器,我们需要重写SourceViewerConfiguration
方法getContentAssistant()
。 最好用Java编辑器的上下文函数Source> Override / Implement Methods ...完成 ,它将为该方法创建一个存根。 现在,我们需要实现此方法并返回IContentAssistant
类型的适当实例。
内容助手由一个或多个内容处理器组成,对于我们要支持的每种内容类型,一个。 由源查看器处理的文档可以分为不同内容类型的几个分区。 这样的分区由分区扫描仪和决定,事实上,我们发现一类XMLPartitionScanner
在包com.bdaum.HTMLEditor.editors
。 此类为我们的文档类型定义了三种不同的内容类型: XML_DEFAULT
, XML_COMMENT
和XML_TAG
。 此外,文档可能包含IDocument.DEFAULT_CONTENT_TYPE
类型的分区。
在新方法getContentAssistant()
,我们首先创建IContentAssistant
的默认实现的新实例,并为其配备一个相同的内容辅助处理器,以处理内容类型XML_DEFAULT
, XML_TAG
和IDocument.DEFAULT_CONTENT_TYPE
。 由于我们不打算在HTML注释中提供帮助,因此我们不为内容类型XML_COMMENT
创建内容辅助处理器。 清单1显示了代码。
清单1. getContentAssistant
public IContentAssistant getContentAssistant(SourceViewer sourceViewer) {
// Create content assistant
ContentAssistant assistant = new ContentAssistant();
// Create content assistant processor
IContentAssistProcessor processor = new HtmlContentAssistProcessor();
// Set this processor for each supported content type
assistant.setContentAssistProcessor(processor, XMLPartitionScanner.XML_TAG);
assistant.setContentAssistProcessor(processor, XMLPartitionScanner.XML_DEFAULT);
assistant.setContentAssistProcessor(processor, IDocument.DEFAULT_CONTENT_TYPE);
// Return the content assistant
return assistant;
}
实施内容辅助处理器
HtmlContentAssistProcessor
类尚不存在。 通过单击黄色的QuickFix灯泡创建它。 在这个新类中,我们只需要完成从IContentAssistProcessor
接口继承的预生成方法。 目前最让我们感兴趣的方法是computeCompletionProposals()
。 此方法返回一个CompletionProposal instances
数组,每个CompletionProposal instances
必须提供一个。 例如,我们可以提供所有HTML标签的集合以供选择。 但是,我们希望它更加复杂。 当在编辑器中选择了文本范围时,我们希望提供样式标签的集合,可以使用这些样式标签包装文本。 否则,我们提供用于创建新HTML结构的标签。 图3和4显示了我们想要实现的目标。
图3. structProposal
图4. styleProposal

因此,首先从编辑器的SourceViewer
实例检索当前选择(请参见清单2)。
清单2.computeCompletionProposals
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer,
int documentOffset) {
// Retrieve current document
IDocument doc = viewer.getDocument();
// Retrieve current selection range
Point selectedRange = viewer.getSelectedRange();
然后创建一个ArrayList instance
以收集生成的ICompletionProposal
实例,如清单3所示。
清单3.computeCompletionProposals(续)
List propList = new ArrayList();
如果选择了文本范围,请检索选择的文本并计算样式标签的建议,如清单4所示。
清单4.computeCompletionProposals(续)
if (selectedRange.y > 0) {
try {
// Retrieve selected text
String text = doc.get(selectedRange.x, selectedRange.y);
// Compute completion proposals
computeStyleProposals(text, selectedRange, propList);
} catch (BadLocationException e) {
}
} else {
否则,请尝试从文档中检索限定词,如清单5所示。这种限定词由部分输入HTML标记的所有字符组成,用于限制可能的提议集合。
清单5.computeCompletionProposals(续)
// Retrieve qualifier
String qualifier = getQualifier(doc, documentOffset);
// Compute completion proposals
computeStructureProposals(qualifier, documentOffset, propList);
}
最后,将完成建议列表转换为一个数组,并将该数组作为结果返回,如清单6所示。
清单6.computeCompletionProposals(续)
// Create completion proposal array
ICompletionProposal[] proposals = new ICompletionProposal[propList.size()];
// and fill with list elements
propList.toArray(proposals);
// Return the proposals
return proposals;
}
构建一个限定词
现在,让我们看看如何从当前文档中检索限定词。 我们需要实现方法getQualifier()
,如清单7所示。
清单7. getQualifier
private String getQualifier(IDocument doc, int documentOffset) {
// Use string buffer to collect characters
StringBuffer buf = new StringBuffer();
while (true) {
try {
// Read character backwards
char c = doc.getChar(--documentOffset);
// This was not the start of a tag
if (c == '>' || Character.isWhitespace(c))
return "";
// Collect character
buf.append(c);
// Start of tag. Return qualifier
if (c == '<')
return buf.reverse().toString();
} catch (BadLocationException e) {
// Document start reached, no tag found
return "";
}
}
}
这很简单。 从当前文档偏移量开始,我们向后读取文档字符。 当我们检测到方括号时,我们找到了标签的开头,并在颠倒了顺序后返回了所收集的字符。 在所有其他无法找到标签开头的情况下,我们都会返回空字符串。 在这种情况下,建议集不受限制。
编制完成建议
现在,让我们汇编提案集。 清单8显示了构成这些提议的一组相关标签。 如果愿意,可以添加更多内容。
清单8.提案集合
// Proposal part before cursor
private final static String[] STRUCTTAGS1 =
new String[] { "<P>", "<A SRC=\"", "<TABLE>", "<TR>", "<TD>" };
// Proposal part after cursor
private final static String[] STRUCTTAGS2 =
new String[] { "", "\"></A>", "</TABLE>", "</TR>", "</TD>" }
如您所见,我们将每个标记建议分为两部分:在计划的光标位置之前的一部分和在计划的光标位置之后的一部分。 清单9显示了可编译这些提议的方法computeStructureProposals()
。
清单9.computeStructureProposals
private void computeStructureProposals(String qualifier, int documentOffset, List propList) {
int qlen = qualifier.length();
// Loop through all proposals
for (int i = 0; i < STRUCTTAGS1.length; i++) {
String startTag = STRUCTTAGS1[i];
// Check if proposal matches qualifier
if (startTag.startsWith(qualifier)) {
// Yes -- compute whole proposal text
String text = startTag + STRUCTTAGS2[i];
// Derive cursor position
int cursor = startTag.length();
// Construct proposal
CompletionProposal proposal =
new CompletionProposal(text, documentOffset - qlen, qlen, cursor);
// and add to result list
propList.add(proposal);
}
}
}
我们遍历标签数组并选择所有以指定限定词开头的标签。 对于每个选定的标签,我们创建一个新的CompletionProposal
实例。 对于参数,我们传递完整的标记文本,应插入此文本的位置,文档中应替换的文本的长度(换句话说,限定符长度)以及相对于开始位置的计划光标位置插入的文本。
此方法将为我们提供所见即所得(“所见即所得”)完成建议。 内容助手的弹出窗口将以与选择提案时将其插入文档中完全相同的形式列出提案。
处理复杂的建议
先前的方法不适用于我们仍然必须实现的方法computeStyleProposals()
。 在这里,我们需要将选定的文本包装到选定的样式标签中,并用此新字符串替换文档中的选定文本。 由于这种替换可以是任意长度,因此在内容助手建议选择窗口中显示它是没有意义的。 相反,最好是选择一个简短但有意义的标签,并在选择特定样式建议后立即显示一个包含完整替换文本的预览窗口。 我们可以通过使用CompletionProposal()
构造函数的扩展形式来实现这种行为。
清单10显示了我们要支持的样式标签以及相关的标签。 同样,您可能想要添加更多。
清单10.样式标签的集合
private final static String[] STYLETAGS = new String[] {
"b", "i", "code", "strong"
};
private final static String[] STYLELABELS = new String[] {
"bold", "italic", "code", "strong"
};
清单11显示了方法computeStyleProposals()
。
清单11. computeStyleProposals
private void computeStyleProposals(String selectedText, Point selectedRange, List propList) {
// Loop through all styles
for (int i = 0; i < STYLETAGS.length; i++) {
String tag = STYLETAGS[i];
// Compute replacement text
String replacement = "<" + tag + ">" + selectedText + "</" + tag + ">";
// Derive cursor position
int cursor = tag.length()+2;
// Compute a suitable context information
IContextInformation contextInfo =
new ContextInformation(null, STYLELABELS[i]+" Style");
// Construct proposal
CompletionProposal proposal = new CompletionProposal(replacement,
selectedRange.x, selectedRange.y, cursor, null, STYLELABELS[i],
contextInfo, replacement);
// and add to result list
propList.add(proposal);
}
}
对于每个受支持的样式标签,我们构造一个替换字符串并创建一个新的完成建议。 当然,该解决方案相当简单。 适当的实现将仔细检查替换字符串。 如果此字符串包含标签,我们将相应地对该字符串进行分段,并将每个单独的段分别包围在新样式标签之间。
显示更多信息
CompletionProposal()
构造函数中的前四个参数与方法computeStructureProposals()
中的含义相同(替换字符串,插入点,替换文本的长度以及相对于插入点的光标位置)。 第五个参数(在本例中为null
接受一个图像实例。 该图像将显示在弹出窗口中相应条目的左侧。 第六个参数接受提案选择窗口中显示的显示标签。 参数7用于IContextInformation
实例,我们将在稍后讨论。 最后,参数8接受选择建议时应在附加信息窗口中显示的文本。 但是,仅提供此参数的值不足以实际获得这样的信息窗口。 我们必须相应地配置内容助手。 同样,这是在XMLConfiguration
类中完成的。 我们只需将清单12中所示的行添加到方法getContentAssistant()
。
清单12.添加到getContentAssistant
assistant.setInformationControlCreator(getInformationControlCreator(sourceViewer));
这里会发生什么? 首先,我们从当前的源查看器配置中获取IInformationControlCreator
类型的实例。 此实例是一个工厂,负责创建类DefaultInformationControl
实例,该类将负责管理信息窗口。 然后,我们告诉内容助理有关该工厂的信息。 选择完成建议后,内容助手最终将使用此工厂来创建新的信息控件实例。
格式化信息文本
默认情况下,此信息控件实例将其他信息文本显示为纯文本。 但是,可以添加一些精美的文本表示。 例如,我们可能希望以粗体打印所有标签。 要做到这一点,我们需要配置DefaultInformationControl
被创建的实例IInformationControlCreator
相应。 唯一的方法是使用不同的IInformationControlCreator
,这可以通过重写XMLConfiguration
方法getInformationControlCreator()
。
清单13显示了类SourceViewerConfiguration
中此方法的标准实现。
清单13. getInformationControlCreator
public IInformationControlCreator getInformationControlCreator
(ISourceViewer sourceViewer) {
return new IInformationControlCreator() {
public IInformationControl createInformationControl(Shell parent) {
return new DefaultInformationControl(parent);
}
};
}
我们通过将类型为DefaultInformationControl.IInformationPresenter
的文本演示者添加到DefaultInformationControl()
构造函数来修改DefaultInformationControl
实例的创建,如清单14所示。
清单14.添加文本演示者
return new DefaultInformationControl(parent, presenter);
剩下要做的就是实现这个文本演示器,如清单15所示。
清单15.文本演示者
private static final DefaultInformationControl.IInformationPresenter
presenter = new DefaultInformationControl.IInformationPresenter() {
public String updatePresentation(Display display, String infoText,
TextPresentation presentation, int maxWidth, int maxHeight) {
int start = -1;
// Loop over all characters of information text
for (int i = 0; i < infoText.length(); i++) {
switch (infoText.charAt(i)) {
case '<' :
// Remember start of tag
start = i;
break;
case '>' :
if (start >= 0) {
// We have found a tag and create a new style range
StyleRange range =
new StyleRange(start, i - start + 1, null, null, SWT.BOLD)
// Add this style range to the presentation
presentation.addStyleRange(range);
// Reset tag start indicator
start = -1;
}
break;
}
}
// Return the information text
return infoText;
}
};
该处理在方法updatePresentation()
。 此方法接收要显示的文本和默认的TextPresentation
实例。 我们只循环文本的字符,并为文本中找到的每个XML标签的文本表示实例添加新的样式范围。 在这些新样式范围中,我们保留前景色和背景色不变,但将字体样式设置为粗体。
上下文信息
现在,让我们看一下在方法computeStyleProposals()
创建的ContextInformation
实例。 在将提案插入文档后,将显示此上下文信息。 它可以用于通知用户有关成功完成申请的建议。 但是,仅将ContextInformation
实例传递给CompletionProposal()
构造函数是不够的。 我们还必须通过完成方法getContextInformationValidator()
来为此上下文信息提供一个验证器。 清单16显示了它是如何完成的。
清单16. getContextInformationValidator
public IContextInformationValidator getContextInformationValidator() {
return new ContextInformationValidator(this);
}
在这里,我们使用了默认的ContextInformationValidator
实现。 该验证器将检查要显示的上下文信息是否包含在由computeContextInformation()
方法返回的上下文信息项的数组中。 否则,将不会显示该信息。 因此,我们还必须完成方法computeContextInformation()
,如清单17所示。
清单17.computeContextInformation
public IContextInformation[] computeContextInformation(ITextViewer viewer,
int documentOffset) {
// Retrieve selected range
Point selectedRange = viewer.getSelectedRange();
if (selectedRange.y > 0) {
// Text is selected. Create a context information array.
ContextInformation[] contextInfos = new ContextInformation[STYLELABELS.length];
// Create one context information item for each style
for (int i = 0; i < STYLELABELS.length; i++)
contextInfos[i] = new ContextInformation(null, STYLELABELS[i]+" Style");
return contextInfos;
}
return new ContextInformation[0];
}
在这里,我们只为每个样式标签创建一个IContextInformation
项。 当然,该解决方案相当简单。 更高级的实现将查看所选文本的周围环境,并确定哪些样式标签实际应用于所选文本。
如果我们不想实现此方法,我们仍然可以选择实现自己的IContextInformationValidator
,该方法始终返回true。
激活助手
至此,我们已经完成了新内容助手的主要逻辑。 但是,当我们测试该插件时,我们发现按Ctrl +空格键仍然没有任何React。 当然为什么呢? 当按下此组合键时,源查看器仍然不知道我们想要完成建议列表。
在独立的SWT / JFace应用程序中,我们将向源查看器添加一个验证侦听器(请参见清单18),并检查此键组合。 按下Ctrl +空格键将触发内容辅助操作,并否决按键事件,以便源查看器不再对其进行任何处理。
清单18. VerifyKeyListener
sourceViewer.appendVerifyKeyListener(
new VerifyKeyListener() {
public void verifyKey(VerifyEvent event) {
// Check for Ctrl+Spacebar
if (event.stateMask == SWT.CTRL && event.character == ' ') {
// Check if source viewer is able to perform operation
if (sourceViewer.canDoOperation(SourceViewer.CONTENTASSIST_PROPOSALS))
// Perform operation
sourceViewer.doOperation(SourceViewer.CONTENTASSIST_PROPOSALS);
// Veto this key press to avoid further processing
event.doit = false;
}
}
});
但是,在工作台编辑器设置中-就像我们HTML编辑器插件一样-我们不需要深入研究事件处理的细节。 在这里,我们宁愿创建一个适当的TextOperationAction
(请参见清单19)来调用内容辅助操作。 为此,我们扩展了HTMLEditor
类中的createActions()
方法。 只要确保在Package SampleHTMLEditor
包SampleHTMLEditor
创建一个文件SampleHTMLEditorPluginResources.properties
即可满足对插件资源包的请求!
清单19. TextOperationAction
private static final String CONTENTASSIST_PROPOSAL_ID =
"com.bdaum.HTMLeditor.ContentAssistProposal";
protected void createActions() {
super.createActions();
// This action will fire a CONTENTASSIST_PROPOSALS operation
// when executed
IAction action =
new TextOperationAction(SampleHTMLEditorPlugin.getDefault().getResourceBundle(),
"ContentAssistProposal", this, SourceViewer.CONTENTASSIST_PROPOSALS);
action.setActionDefinitionId(CONTENTASSIST_PROPOSAL_ID);
// Tell the editor about this new action
setAction(CONTENTASSIST_PROPOSAL_ID, action);
// Tell the editor to execute this action
// when Ctrl+Spacebar is pressed
setActionActivationCode(CONTENTASSIST_PROPOSAL_ID,' ', -1, SWT.CTRL);
}
现在我们可以再次测试我们的插件了。 现在,我们应该能够通过按Ctrl +空格键来调用内容助手。 您可能想尝试助手的其他行为,具体取决于是否已选择文本。
不过,我们可以添加更多代码。 例如,当键入“ <”字符时,内容助手可以自动激活自己。 这可以通过向内容助手处理器指定此自动激活字符来完成(由于我们可以为每种文档内容类型使用特定的处理器,因此我们还可以为每种内容类型使用不同的自动激活字符)。 为此,我们完成了HtmlContentAssistProcessor
类中方法getCompletionProposalAutoActivationCharacters
的定义,如清单20所示。
清单20. getCompletionProposalAutoActivationCharacters
public char[] getCompletionProposalAutoActivationCharacters() {
return new char[] { '<' };
}
另外,我们必须启用自动激活并设置自动激活延迟。 这是在XMLConfiguration
类中完成的。 我们将清单21中所示的以下行添加到方法getContentAssistant()
。
清单21.添加到getContentAssistant
assistant.enableAutoActivation(true);
assistant.setAutoActivationDelay(500);
最后,我们可能希望更改内容助手弹出窗口的背景颜色,以使其与附加信息窗口区分开。 因此,我们在方法getContentAssistant()
添加了两行,如清单22所示。
清单22.添加到getContentAssistant
Color bgColor = colorManager.getColor(new RGB(230,255,230));
assistant.setProposalSelectorBackground(bgColor);
注意,我们使用XMLConfiguration
实例的颜色管理器来创建新颜色。 这样一来,我们就无需再处理不再需要的颜色,因为颜色管理器会关心废弃情况。
进阶概念
现在,在我们为HTML管理器成功实现内容助手之后,您可能想知道基于模板的内容助手的工作原理以及如何实现。 这些内容助手-我们从Java源代码编辑器中了解到-具有一项特殊功能:可以对完成建议进行参数化。 用户可以修改此类建议中的特定名称,从而使该名称的所有出现都在整个建议中同步更新。
坏消息是此功能是Eclipse Java Development Toolkit(JDT)插件的一部分,因此在缺少该插件的应用程序中不可用-独立的基于SWT / JFace的应用程序或最小的Eclipse平台。 好消息是此功能的源代码可用,并且很难适应其他环境。 特别地,所述类ExperimentalProposal
从包org.eclipse.jdt.internal.ui.text.java
和类型ILinkedPositionListener
, LinkedPositionUI
和LinkedPositionManager
从包org.eclipse.jdt.internal.ui.text.link
实现此功能。
翻译自: https://www.ibm.com/developerworks/java/library/os-ecca/index.html