Skip to main content

Building a CDT-based editor, Part 5: Using the PDOM for code completion

Understanding how the CDT performs code completion

Matthew Scarpino (mattscar@yahoo.com), Java Developer, Eclipse Engineering, LLC
Matthew Scarpino is a project manager and Java developer at Eclipse Engineering LLC. He is the lead author of SWT/JFace in Action and made a minor but important contribution to the Standard Widget Toolkit (SWT). He enjoys Irish folk music, marathon running, the poetry of William Blake, and the Graphical Editing Framework (GEF).

Summary:  This article, fifth in a five-part "Building a CDT-based editor" series, shows how the C/C++ Development Tooling (CDT) performs code completion. This is one of the CDT's most useful capabilities because it reduces the amount of code the user needs to type and remember. Also, it provides a complete example of how the CDT uses the Persisted Document Object Model (PDOM).

View more content in this series

Date:  12 Dec 2006
Level:  Intermediate
Activity:  776 views

All about code completion

Figure 1 demonstrates one of the main reasons I love the CDT. When I use a C++ class, I don't need to remember every member method and variable. I just type the class name and a dot, and the CDT gives me a list of choices. I click Enter, and it inserts my selection. If I type part of a name or keyword, Ctrl+Space finishes it for me. You can say that code completion dumbs down the coding process, but I prefer to think it saves my precious brain cells for more important tasks, like Sudoku.


Figure 1. The glory of CDT code completion
The glory of CDT code completion

To be precise, code completion is one of the two capabilities that fall under the general heading of content assist. The second capability is called context information, and it works like a tool tip that appears during the editing process. Both perform similar tasks: They provide text information when the user enters specific character sequences. The CDT, alas, makes no use of context information, so I'll stick to code completion in this article.

Code completion saves the user time and effort, but adding this function takes a significant amount of development effort. Not only do you need to create the list of proposals but you also need to search through the parser's abstract syntax tree (AST) to figure out which proposals to list. This article describes both in detail and shows how to customize the CDT code completion with your own proposals. This ties together the CEditor and the PDOM parser, so it makes a fine conclusion for this series.

The code completion process involves four main steps:

  1. Activating the code completion
  2. Finding the ASTCompletionNode
  3. Generating the code completion proposals
  4. Displaying the code completion proposals

As always, I've added code completion to the Bare Bones C/C++ Development Tooling (BBCDT). (See Download for the code.)

Activating code completion

I'll start with how the editor recognizes when code completion needs to be performed. The CViewer performs the CDT editor's event processing and has so many responsibilities that its configuration is bundled in a separate object: the CSourceViewerConfiguration. In Part 2, I explain how this enables text presentation and reconciling. It also creates the editor's ContentAssistant, which has the primary responsibility for managing code completion.

Like many managers, the ContentAssistant does little besides delegate. Specifically, it creates an IContentAssistantProcessor implementation for each type of Document that can appear in the editor. This processor performs the main work of code completion; it provides the auto-activation characters that begin the process and returns proposals to complete the code. The CDT editor only has one type of document, the DEFAULT_CONTENT_TYPE, so the ContentAssistant creates a single processor to handle code completion. This important object is the CCompletionProcessor2.

When new characters are entered into the CDT editor, the ContentAssistant compares the text to a list of auto-activation characters. To get this list, the assistant accesses the CCompletionProcessor2 and calls getCompletionProposalAutoActivationCharacters(), which returns an array of char[]s. If the new text matches any of these arrays, the assistant starts code completion.

But before I go on, I want to discuss how the CDT generates this array of char[]s. Figure 2 shows the properties dialog that appears when you go to Window > Preferences ... > C/C++ > Editor > Content Assist.


Figure 2. CDT Content Assist
CDT Content Assist

The characters ., ->, and :: are chosen by default to begin the code completion. To add your own auto-activation characters, the class to modify is ContentAssistPreference in the org.eclipse.cdt.internal.ui.text.contentassist package. The Ctrl+Space command isn't set by preferences, but is directly handled by the CSourceViewer.

When the ContentAssistant matches one of these char arrays to incoming text, it begins the code completion process. That is, it tells the CCompletionProcessor2 to generate proposals containing text that can best complete the code.

Finding the ASTCompletionNode

To come up with these proposals, it isn't enough to know which characters were just entered. The CCompletionProcessor2 needs to determine where this text fits into the general code structure. Specifically, it needs to find the text's corresponding AST node, referred to as the completion node. With the completion node, it can gather additional information such as the node's scope and binding.

To acquire this node, the processor determines the ILanguage of the current WorkingCopy and calls ILanguage.getCompletionNode(). This is similar to how the indexer starts the parsing process by calling ILanguage.getASTTranslationUnit(). In fact, both methods tell the ILanguage's parser to re-parse the document.

But first, the processor tells the DOMScanner to stop scanning when it reaches the code completion point. During the re-parsing, the scanner checks this offset whenever it detects an identifier. When it reaches the code completion point, the scanner returns the identifier as part of a token of type IToken.tCOMPLETION and stops scanning.

In Part 4, I explain how the new parsers create IASTNames for identifiers in the code. Each time it creates an IASTName, the parser also checks whether the current token is of the IToken.tCOMPLETION type. If so, it creates an ASTCompletionNode. There may be multiple IASTNames associated with a given completion node.

Generating code completion proposals

As soon as it has the completion node, the CCompletionProcessor2 creates an empty ArrayList to contain CCompletionProposals. Then it finds classes that can populate this list. Specifically, it looks for extensions of the CDT extension point org.eclipse.cdt.ui.completionContributors.

According to the schema, an extension must have a <contributor> element that contains a required <class> and an optional <id> and <priority>. By default, the CDT provides five such extensions, and they are shown in Listing 1.


Listing 1. Default code completion extensions
  
<extension point="org.eclipse.cdt.ui.completionContributors">
   <contributor
      class="org.eclipse.cdt.internal.ui.text.contentassist.DOMCompletionContributor"
      id="DOM"
      priority="1"/>
   <contributor
      class="org.eclipse.cdt.internal.ui.text.template.TemplateEngine"
      id="CodeTemplates"
      priority="2"/>
   <contributor
      class="org.eclipse.cdt.internal.ui.text.contentassist.PDOMCompletionContributor"
      id="PDOM"
      priority="3"/>
   <contributor
      class="org.eclipse.cdt.internal.ui.text.contentassist.KeywordCompletionContributor"
      id="Keywords"
      priority="10"/>
   <contributor
      class="org.eclipse.cdt.internal.ui.text.contentassist.HelpCompletionContributor"
      id="Help"
      priority="99"/>
</extension>

The <class> element specifies the location of an implementation of ICompletionContributor. This interface contains a single method: contributeCompletionProposals(). This is the method to know if you want to customize code completion, so I'll list its parameters here:

TextViewer
The editor's viewer, which allows you to directly access the StyledText widget
int
The offset of the code completion point within the document
WorkingCopy
The current, possibly unsaved, text in the editor
ASTCompletionNode
The completion node, a subclass of ASTName
String
The text preceding the completion node, called the prefix
ArrayList
The list of proposals created by the CCompletionProcessor2

The first of the CDT's contributors, the DOMCompletionContributor, iterates through each of the node's IASTNames and finds their bindings. If the IBinding is an IFunction, the contributor forms a StringBuffer containing the function's name and parameters -- separated by commas and surrounded by parentheses. If the binding corresponds to a variable, it creates a StringBuffer containing the variable's name, a colon, and its type. In either case, the DOMCompletionContributor uses the StringBuffer to initialize a new CCompletionProposal.

The second contributor, the TemplateEngine, inserts a common code pattern when a specific word is entered. For example, if you start a new statement with the for keyword, entering Ctrl+Space gives you the template options shown in Figure 3. When you click Enter, the CDT produces generic code representing the chosen type of for loop.


Figure 3. CDT template completion
CDT template completion

It's simple for the TemplateEngine to create CTemplateProposals because it doesn't care about the completion node or the code's AST. Instead, it just matches specific keywords to template proposals. You can configure how the CDT's templates work by going to Window > Preferences ... > C/C++ > Editor > Templates. Or, if you want to add your own, change the default-templates.xml file in the templates folder in the org.eclipse.cdt.ui plug-in or add an extension to the org.eclipse.ui.editors.templates point.

The PDOMCompletionContributor is a subclass of the DOMCompletionContributor, and the two classes function similarly. What makes the PDOMCompletionContributor different is that it finds the IBindings for the completion node by searching through the PDOM database described in Part 4. That is, it creates an IPDOMVisitor and visits each of its PDOMBindings. If the binding's name starts with the prefix, it creates a proposal just like that of the DOMCompletionContributor.

Note: As I write this, the PDOMCompletionContributor doesn't work correctly. However, this will likely change in the near future.

The KeywordCompletionContributor is the simplest of the five completion contributors and completes keywords that have already been started. Specifically, it creates two arrays, ckeywords and cppkeywords, whose Strings are taken from the Keywords class in org.eclipse.cdt.core.parser. If any element of the two arrays begins with the prefix, the contributor creates a corresponding CCompletionProposal.

I'm going to skip the HelpCompletionContributor because it requires an explanation of how the CDT's Help system works. Instead, I'll discuss the goal of the completion contributors: the CCompletionProposal. It contains more than just the completion String. It also stores the style of the String, the position in the document where the String should be placed, and any Image that should be associated with the proposal.

After the CCompletionProcessor2 finds classes to create proposals, it checks for classes that can sort them. It looks through the workbench's plug-in extensions searching for an extension of the org.eclipse.cdt.ui.proposalFilters extension point. The schema for this extension point, like that for completion contributors, consists primarily of a <class> element. In this case, the class must implement the IProposalFilter interface, whose filterProposals() method accepts an array of CCompletionProposals and returns a filtered array of CCompletionProposals.

If you don't create your own extension, the CCompletionProcessor2 will sort its proposals with a DefaultProposalFilter. This puts them in alphabetical order using a CCompletionProposalComparator and removes proposals that have the same name and return type. After these proposals are sorted and filtered, they are ready to be displayed in the editor. I'll discuss this next.

Displaying the code completion proposals

One of the first tasks of the ContentAssistant is to create a CompletionProposalPopup. This is the yellow box in Figure 1 that magically appears to help you complete your code. This remains hidden at first but appears when the ContentAssistant calls its showProposals() method.

This method is important and starts by telling the ContentAssistant to come up with proposals that it can display. Then it creates a Control and configures its size, location, color, and layout. It builds a Table inside the Control and adds a TableItem for each of the proposals. Then it makes the Control visible and waits for the user to make a selection.

Every ICCompletionProposal has an apply() method that describes how the proposal should be inserted into the text if it's selected. In the CDT, the proposal simply accesses the Document, determines what section needs to be updated, and inserts the text. You can add your own functionality by changing this method. Then, when it applies the proposal, the popup hides and the user can continue editing the Document.


Updating the BBCDT

To keep the BBCDT slimmed down, I added code for the DOMCompletionContributor, the PDOMCompletionContributor, and the KeywordCompletionContributor. I would have added the TemplateEngine, but templates require a great deal of code to integrate. I encourage you to try it.

Since the BBCDT doesn't use preferences, I hard-coded all the parameters used by the ContentAssistant to customize the code completion process. I added this code, shown in Listing 2, to the CSourceViewerConfiguration class. As you can see, most of the configuration deals with setting the color scheme of the controls, and to do this, I accessed the Display's system colors, which don't need to be de-allocated after usage.


Listing 2. BBCDT code completion customization

// Create the content assistant
ContentAssistant assistant = new ContentAssistant();
		
// Configure content assistant
assistant.enableAutoInsert(true);
assistant.enableAutoActivation(true);
assistant.setAutoActivationDelay(500);
assistant.enablePrefixCompletion(true);

Display display = fEditor.getEditorSite().getShell().getDisplay();
assistant.setProposalSelectorForeground(display.getSystemColor(SWT.COLOR_CYAN));
assistant.setProposalSelectorBackground(display.getSystemColor(SWT.COLOR_BLACK));
assistant.setContextInformationPopupForeground(display.getSystemColor(SWT.COLOR_BLACK));
assistant.setContextSelectorForeground(display.getSystemColor(SWT.COLOR_BLACK));
assistant.setContextInformationPopupBackground(display.getSystemColor(SWT.COLOR_YELLOW));
assistant.setContextSelectorBackground(display.getSystemColor(SWT.COLOR_YELLOW));
assistant.setProposalPopupOrientation(IContentAssistant.PROPOSAL_OVERLAY);		
assistant.setContextInformationPopupOrientation(IContentAssistant.CONTEXT_INFO_ABOVE);
assistant.setInformationControlCreator(getInformationControlCreator(sourceViewer));
		
// Create the assist processor
IContentAssistProcessor processor = new CCompletionProcessor2(getEditor());

// Configure the assist processor
((CCompletionProcessor2)processor).setCompletionProposalAutoActivationCharacters
    (new char[] {'.', ':', '>'});
((CCompletionProcessor2)processor).allowAddingIncludes(false);	
((CCompletionProcessor2)processor).orderProposalsAlphabetically(true);
assistant.setContentAssistProcessor(processor, IDocument.DEFAULT_CONTENT_TYPE);

Figure 4 shows how the BBCDT's code completion looks when you type an s and press Ctrl+Space. If you'd like to change the colors, modify the code in the above listing to suit your preferences. You can also add new auto-activation characters by changing the array argument to the setCompletionProposalAutoActivationCharacters method.


Figure 4. BBCDT code completion
BBCDT code completion


Conclusion

Code completion is one of the many aspects of the CDT that is far easier to use than understand. But you don't need to grasp the whole process to incorporate this feature into your applications. You can extend existing completion contributors, such as the KeywordCompletionContributor or the TemplateEngine, or you can add your own contributor by extending the org.eclipse.cdt.ui.completionContributors extension point. Then, if you want to control the order in which the contributions are displayed, just extend the org.eclipse.cdt.ui.proposalFilters point.

When I first set out to learn about the CDT editor, I hadn't realized how complicated its internals were, and it took some time before I had a solid grasp of the basics. It took longer to understand features like the parser and the PDOM. But like so many endeavors, you get out of the CDT what you put into it. The more I came to understand the CDT, the better my custom editor looked and operated.

This series of articles has been very detailed, but I hope my example, the BBCDT, has provided an incentive to learn the tool and sufficient motivation to continue learning.



Download

DescriptionNameSizeDownload method
BBCDT code for Part 5os-ecl-cdt5.zip5MB HTTP

Information about download methods


Resources

Learn

Get products and technologies

Discuss

About the author

Matthew Scarpino is a project manager and Java developer at Eclipse Engineering LLC. He is the lead author of SWT/JFace in Action and made a minor but important contribution to the Standard Widget Toolkit (SWT). He enjoys Irish folk music, marathon running, the poetry of William Blake, and the Graphical Editing Framework (GEF).

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=182867
ArticleTitle=Building a CDT-based editor, Part 5: Using the PDOM for code completion
publish-date=12122006
author1-email=mattscar@yahoo.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers