java swt程序_为SWT应用程序配备内容助手

本文详细介绍如何在基于Eclipse的HTML编辑器中实现内容助手,包括创建提案、处理复杂建议、显示附加信息等,旨在提升编辑体验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

创建一个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_DEFAULTXML_COMMENTXML_TAG 。 此外,文档可能包含IDocument.DEFAULT_CONTENT_TYPE类型的分区。

在新方法getContentAssistant() ,我们首先创建IContentAssistant的默认实现的新实例,并为其配备一个相同的内容辅助处理器,以处理内容类型XML_DEFAULTXML_TAGIDocument.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
structProposal
图4. styleProposal
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 SampleHTMLEditorSampleHTMLEditor创建一个文件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和类型ILinkedPositionListenerLinkedPositionUILinkedPositionManager从包org.eclipse.jdt.internal.ui.text.link实现此功能。


翻译自: https://www.ibm.com/developerworks/java/library/os-ecca/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值