Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Xtext/Documentation

< Xtext
Revision as of 16:05, 31 January 2009 by Sven.efftinge.itemis.de (Talk | contribs) (Changing Configuration)

What is Xtext?

The TMF Xtext project provides a domain-specific language (the grammar language) for description of textual programming languages and domain-specific languages. It is tightly integrated with the Eclipse Modeling Framework (EMF) and leverages the Eclipse Platform in order to provide language-specific tool support. In contrast to common parser generators the grammar language is much simpler but is used to derive much more than just a parser and lexer. From a grammar the following is derived:

  • incremental, Antlr3-based parser and lexer
  • Ecore-based meta models (optional)
  • a serializer, used to serialize instances of such meta models back to a parseable textual representation
  • an implementation of the EMF Resource interface (based on the parser and the serializer)
  • a full-fledged integration of the language into Eclipse IDE
    • syntax coloring
    • navigation (F3, etc.)
    • code completion
    • outline views
    • code templates
    • folding, etc.

The generated artifacts are wired up through a dependency injection framework, which makes it easy to exchange certain functionality in a non-invasive manner. For example if you don't like the default code assistant implementation, you need to come up with an alternative implementation of the corresponding service and configure it via eclipse extension point.

The Grammar Language

The grammar language is the corner stone of Xtext and is defined in itself - of course. It can be found here.

The grammar language is a DSL carefully designed for description of textual languages, based on ANTLR's LL(*) parsing strategy. The main idea is to let users describe the concrete syntax, and to automatically derive an in-memory model (semantic model) from that.

First an example

To get an idea of how it works we'll start by implementing a [simple example] introduced by Martin Fowler. It's mainly about describing state machines used as the (un)lock mechanism of secret compartments.

One of those state machines could look like this:

 events
  doorClosed  D1CL
  drawOpened  D2OP
  lightOn     L1ON
  doorOpened  D1OP
  panelClosed PNCL
 end
 
 resetEvents
  doorOpened
 end
 
 commands
  unlockPanel PNUL
  lockPanel   PNLK
  lockDoor    D1LK
  unlockDoor  D1UL
 end
 
 state idle
  actions {unlockDoor lockPanel}
  doorClosed => active
 end
 
 state active
  drawOpened => waitingForLight
  lightOn    => waitingForDraw
 end
 
 state waitingForLight
  lightOn => unlockedPanel
 end
 
 state waitingForDraw
  drawOpened => unlockedPanel
 end
 
 state unlockedPanel
  actions {unlockPanel lockDoor}
  panelClosed => idle
 end

So we have a bunch of declared events, commands and states. Within states there are references to declared actions, which should be executed when entering such a state. Also there are transitions consisting of a reference to an event and a state. Please read [Martin's description] is it is not clear enough.

In order to implement this language with Xtext you need to write the following grammar:

 language SecretCompartments
 
 generate secretcompartment "http://www.eclipse.org/secretcompartment"
 
 Statemachine :
  'events'
     (events+=Event)+
  'end'
  ('resetEvents'
     (resetEvents+=[Event])+
  'end')?
  'commands'
     (commands+=Command)+
  'end'
  (states+=State)+;
 
 Event :
  name=ID code=ID;
 
 Command :
  name=ID code=ID;
 
 State :
  'state' name=ID
     ('actions' '{' (actions+=[Command])+ '}')?
     (transitions+=Transition)*
  'end';
 
 Transition :
  event=[Event] '=>' state=[State];

In the following the different concepts of the grammar language are explained. We refer to this grammar when useful.

Language Declaration

The first line

language SecretCompartments

declares the name of the language. Xtext leverages Java's classpath mechanism. This means that the name can be any valid Java qualifier. The file name needs to correspond and have the file extension '*.xtext'. This means that the name needs to be "SecretCompartments.xtext" and must be placed in the default package on the Java's class path.

If you want to place it within a package (e.g. 'foo/SecretCompartment.xtext') the first line must read:

language foo.SecretCompartment

The first line can also be used to declare a super language to inherit from. This mechanism is described here.

EPackage declarations

Xtext parsers instantiate Ecore models (aka meta model). An Ecore model basically consists of an EPackage containing EClasses, EDatatypes and EEnums. Xtext can infer Ecore models from a grammar (see MetaModel Inference) but it is also possible to instantiate existing Ecore models. You can even mix this and use multiple existing ecore models and infer some others from one grammar.

EPackage generation

The easiest way to get started is to let Xtext infer the meta model from your grammar. This is what is done in the secret compartment example. To do so just state:

generate secretcompartment "http://www.eclipse.org/secretcompartment"

Which says: generate an EPackage with name secretcompartment and nsURI "http://www.eclipse.org/secretcompartment" (these are the properties needed to create an EPackage).

EPackage import

If you already have created such an EPackage somehow, you could import it using either the name space URI or a resource URI (URIs are an EMF concept):

import "http://www.eclipse.org/secretcompartment"

Using multiple packages

If you want to use multiple EPackages you need to specify aliases like so:

generate secretcompartment "http://www.eclipse.org/secretcompartment"
import "http://www.eclipse.org/anotherPackage" as another

When referring to a type somewhere in the grammar you need to qualify them using that alias (example "another::CoolType"). We'll see later where such type references occur.

Rules

The parsing is based on ANTLR 3, which is a parser generator framework based on an LL(*) parsing algorithm - see http://www.antlr.org/wiki/display/ANTLR3/3.+LL(*)+Parsing+(Excursion).

Basically parsing can be separated in the following phases.

  1. lexing
  2. parsing
  3. linking
  4. validation

Lexer Rules

In the first phase a sequence of characters (the text input) is transformed into a sequence of so called tokens. Each token consists of one or more characters and was matched by a particular lexer rule. In the secret compartments example there are no explicitly defined lexer rules, since it uses lexer rules inherited from the default super language (called org.eclipse.xtext.builtin.XtextBuiltin (see Language Inheritance)), only (the ID rule).

Therein the ID rule is defined as follows:

lexer ID : 
  "('^')?('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'_'|'0'..'9')*"; 

It says that a Token ID starts with an optional '^' character followed by a letter ('a'..'z'|'A'..'Z') or underscore ('_') followed by any number of letters, underscores and numbers ('0'..'9'). Note that this declaration is just a string literal which is passed to a generated ANTLR grammar as is.

The optional '^' is used to escape an identifier for cases where there are conflicts with keywords. It is removed during parsing.

This is the formal definition of lexer rules:

LexerRule :
  'lexer' name=ID ('returns' type=TypeRef)? ':' body=STRING ';'
;

Return types

A lexer rule returns a value, which defaults to a string (type ecore::EString). However, if you want to have a different type you can specify it. For instance, the built-in lexer rule 'INT' is defined like so:

lexer INT returns ecore::EInt : 
  "('0'..'9')+";

Meaning the lexer rule INT returns instances of ecore::EInt. It is possible to define any kind of data type here, which just needs to be an instance of ecore::EDataType. In order to tell the parser how to convert the parsed string to a value of the declared data type, you need to provide your own implementation of 'IValueConverterService'.

Have a look at org/eclipse/xtext/builtin/conversion/XtextBuiltInConverters.java to find out how such an implementation looks like.

The implementation needs to be registered as a service (see Service Framework). This is also the point where you can remove things like quotes form string literals or the '^' from identifiers.

Enum Rules

TODO Not yet implemented

String Rules

TODO Not yet implemented

Parser Rules

The parser reads in a sequence of tokens produced by the lexer and walks through the parser rules.

to be continued.

Model Construction

Meta-Models

The meta model of a textual language describes the structure of its abstract syntax trees (AST).

Xtext uses Ecore EPackages to define meta models. Meta models are declared to be either inferred from the grammar or imported. A meta model's declaration can also define an alias name that is used in other places of the grammar to qualify references to its types. Each language inherits the aliases from its superlanguage, but it can also override them. The default meta model does not have an alias in its declaration. It will contain all types that are referred to without qualifier.

language MyLang

generate MyMetaModel "http://www.mysite.org/myMetaModel" // default meta model
import "http://www.eclipse.org/emf/2002/Ecore" as ecore  // imported meta model with alias

RuleA returns MyType:         // reference to default meta model
  'mytype' name=ID; 
   
RuleB returns ecore::EObject: // reference to imported EPackage
  name=ID;


Meta Model Inference

By using the generate directive, Xtext derives one or more metamodel(s) from the grammar. All elements/types can occur multiple times, but are generated only once.

Type and Package Generation

Xtext creates

an EPackage
  • for each generated package declaration. The name of the EPackage will be set to the first parameter, its nsURI to the second parameter. An optional alias allows to distinguish generated EPackages later. Only one generated package declaration without an alias is allowed per grammar.
an EClass
  • for each return type of a parser rule. If a parser rule does not define a return type, an implicit one with the same name is assumed. You can specifiy more than one rule that return the same type but only one EClass will be gererated.
  • for each type defined in an action or a cross-reference.
an EDatatype
  • for each return type of a lexer rule.

All EClasses and EDatatypes are added to the EPackage referred to by the alias provided in the type reference they were created from.

Feature and Type Hierarchy Generation

While walking through the grammar, the algorithm keeps track of a set of the currently possible return types to add features to.

  • Entering a parser rule the set contains only the return type of the rule.
  • Entering a group in an alternative the set is reset to the same state it was in when entering the first group of this alternative.
  • Leaving an alternative the set contains the union of all types at the end of each of its groups.
  • After an optional element, the set is reset to the same state it was before entering it.
  • After a mandatory (non-optional) rule call or mandatory action the set contains only the return type of the called rule or action.
  • An optional rule call or optional action does not modify the set.
  • A rule call or an action is optional, if its cardinality is '?' or '+'.

While iterating the parser rules and Xtext creates

an EAttribute in each current return type
  • of type EBoolean for each feature assignment using the '?=' operator. No further EReferences or EAttributes will be generated from this assignment.
  • for each assignment with the '=' or '+=' operator calling a lexer rule. Its type will be the return type of the called rule.
an EReference in each current return type
  • for each assignment with the '=' or '+=' operator in a parser rule calling a parser rule. The EReferences type will be the return type of the called parser rule.
  • for each action. The reference's type will be set to the return type of the current calling rule.

Each EAttribute or EReference takes its name from the assignment/action that caused it. Multiplicities will be 0...1 for assignments with the '=' operator and 0...* for assignments with the '+=' operator.

Furthermore, each type that is added to the currently possible return types automatically inherits from the current return type of the parser rule. You can specify additional common supertypes by means of "artificial" parser rules, that are never called, e.g.

CommonSuperType:
  SubTypeA | SubTypeB | SubTypeC;

Feature Normalization

As a last step, the generator examines all generated EClasses and lifts up similar features to supertypes if there is more than one subtype and the feature is defined in every subtypes. This does even work for multiple supertypes.

Error Conditions

The following conditions cause an error

  • An EAttribute or EReference has two different types or differnt cardinality.
  • There are an EAttribute and an EReference with the same name in the same EClass.
  • There is a cycle in the type hierarchy.
  • An new EAttribute, EReference or supertype is added to an imported type.
  • An EClass is added to an imported EPackage.
  • An undeclared alias is used.
  • An imported metamodel cannot be loaded.

Importing existing Meta Models

With the import directive in Xtext you can refer to existing Ecore metamodels and reuse the types that are declared in an EPackage. Xtext uses this technique by itself to leverage Ecore datatypes.

import "http://www.eclipse.org/emf/2002/Ecore" as ecore;

Specify an explicit return type to reuse such imported types. Note that this even works for lexer rules.

lexer INT returns ecore::EInt : "('0'..'9')+";

Language Inheritance

This concept is about to be changed in the future. Since simple inheritance is too restrictive, we'll come up with something more mixin-like..

Xtext support language inheritance. By default (implicitly) each language extends a language called *org.eclipse.xtext.builtin.XtextBuiltin* and is defined as follows:

 abstract language org.eclipse.xtext.builtin.XtextBuiltIn_Temp
 
 import "http://www.eclipse.org/emf/2002/Ecore" as ecore;
 
 
 lexer ID : 
   "('^')?('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'_'|'0'..'9')*";
 
 lexer INT returns ecore::EInt : 
   "('0'..'9')+";
 
 lexer STRING : 
   "
   '\"' ( '\\\\' ('b'|'t'|'n'|'f'|'r'|'\\\"'|'\\''|'\\\\') | ~('\\\\'|'\"') )* '\"' | 
   '\\'' ( '\\\\' ('b'|'t'|'n'|'f'|'r'|'\\\"'|'\\''|'\\\\') | ~('\\\\'|'\\'') )* '\\''
   ";
 
 lexer ML_COMMENT : 
   "'/*' ( options {greedy=false;} : . )* '*/' {$channel=HIDDEN;}";
 
 lexer SL_COMMENT : 
   "'//' ~('\\n'|'\\r')* '\\r'? '\\n' {$channel=HIDDEN;}";
 
 lexer WS : 
   "(' '|'\\t'|'\\r'|'\\n')+ {$channel=HIDDEN;}";
 
 lexer ANY_OTHER : 
   ".";

Just ignore the grammar if you don't yet understand it. It basically provides some commonly used lexer rules which can be used in all grammars.

Architectural Overview

TMF Xtext itself and every language infrastructure developed with TMF Xtext is configured and wired-up using dependency injection. We use Google Guice as the underlying framework, and haven't build much on top of it as it pretty much does what we need. So instead of describing how google guice works, we refer to the website, where additional information can be found [1].

Using DI allows people to reconfigure and hook in all components. This does not mean that everything which gets configured using DI (we use it a lot) is automatically public API. But we don't forbid use of non-public API, you are all old enough ;-). It just means that it might change.

Service Scopes

We have introduced so called service scopes (IServiceScope) and each language corresponds to a service scope. There is a generated singleton in the I[MyLangauge] interface called SCOPE which should be used to obtain the service scope. TMF Xtext holds a static registry containing all active service scopes. In addition for each service scope there is one Guice Injector, which has to be configured before the language infrastructure can be used. There are only very rare cases where one need to access the global registry or the service scope. We use them in Junit tests and when injecting stuff into something where the creation is not controlled by TMF Xtext. In Eclipse this is the case, when hooking in using extension points. We have a ServiceInjectingExecutableExtensionFactory which can be used to inject things into executable extensions, such as a TextEditor.

Having an injector per service scope, basically means that each language has it's own configuration. You never share any components with another language. You of course use the same implementation, but never the same instances.

The interesting Xtext-specific part you should know about is the hooks, where the actually bootstrapping is done (i.e. the initial configuration).

How to setup the language infrastructure

There are two different so called 'setup' classes, containing 'doSetup()'-methods, which when called do the global setup. They are generated and named:

- [MyLanguage]StandaloneSetup
- [MyLanguage]UISetup

The first one is intended to be used when only the runtime is needed, i.e. those cases where you don't run the whole IDE but only need the parsers, etc.. Components configured for runtime, should only have dependency to things which can be run outside of Eclipse. The latter is used within Eclipse and also configures the tooling-related components, such as editors, code completion, outline view, etc.. The UISetup also calls the StandaloneSetup.

The Setup methods

In addition to the Guice-related configuration, there is also need for other kind of setup. For instance, we need to register EMF Packages, EMF Resource Factories and Validators. Those things are done directly in the generated StandaloneSetup class. We do not use any extension points declared for that purpose.

Modules

The Guice Injector configuration is done through the use of Modules (also a Guice concept). The generator provides two modules when first called, one for runtime ([MyLanguage]RuntimeModule) and one for UI ([MyLanguage]UIModule). These modules are initially empty and intended to be manually edited when needed. These are also the modules used directly by the setup methods. By default these modules extend a fully generated module.

Generated Modules

The fully generated modules (never touch them!) are called Abstract[MyLanguage]RuntimeModule and Abstract[MyLanguage]UiModule resp. They contain all components which have been generated specifically for the language at hand. Examples are: the generated parsers, serializer or for UI a proposal provider for content assist is generated. What goes into the modules depends on how your generator is configured.

Default Modules

Finally the fully generated modules extend the DefaultRuntimeModule (resp. DefaultUiModule), which contains all the default configuration. The default configuration consists of all components for which we have generic default implementations (interpreting as opposed to generated). Examples are all the components used in linking, the outline view, hyperlinking and navigation.

Changing Configuration

We use the primary modules ([MyLanguage]RuntimeModule and [MyLanguage]UiModule) in order to change the configuration. The class is initially empty and has been generated only to allow for arbitrary customization.

In order to provide a simple and convenient way, in TMF Xtext every module extends AbstractXtextModule. This class allows to write bindings like so:

public Class<? extends MyInterface> bind[anyname]() {
    return MyInterfaceImpl.class;
}


Such a method will be interpreted as a binding from MyInterface to MyInterfaceImpl. Note that you simply have to override a method from a super class (e.g. from the generated or default module) in order to change the respective binding. Although this is a convenient and simple way, you have of course also the full power of Guice, i.e. you can override the Guice method void bind(Binding) and do what every you want.

Runtime Architecture

Value Converters

Linking

The linking feature allows for specification of cross references within an Xtext grammar. The following things are needed for the linking:

  1. declaration of a cross link in the grammar (at least in the meta model)
  2. specification of linking semantics

Declaration of cross links

In the grammar a cross reference is specified using square brackets.

CrossReference :
  '[' ReferencedEClass ('|' lexerRuleName)? ']'

Example:

ReferringType :
  'ref' referencedObject=[Entity|ID];

The meta model derivation would create an EClass 'ReferringType' with an EReference 'referencedObject' of type 'Entity' (containment=false). The referenced object would be identified by an ID (can be omitted) and the surrounding information (see scoping).

Example: While parsing a given input string, say

ref Entity01

Xtext produces an instance of 'ReferringType'. After this parsing step it enters the linking phase and tries to find an instance of Entity using the parsed text 'Entity01'.

Specification of linking semantics

The default ILinker implementation is provided with an instance of ILinkingService. Although the default linking behavior is appropriate in many cases there might be scenarios where this is not sufficient. For each grammar a linking service can be implemented/configured, which implements the following interface:

public interface ILinkingService extends ILanguageService {
 
	/**
	 * Returns all EObjects referenced by the given link text in the given context.
	 */
	List<EObject> getLinkedObjects(EObject context, EReference reference, LeafNode text);
 
	/**
	 * Returns the textual representation of a given object as it would be serialized in the given context.
	 * 
	 * @param object
	 * @param reference
	 * @param context
	 * @return the text representation.
	 */
	String getLinkText(EObject object, EReference reference, EObject context);
}

The method getLinkedObjects is directly related to this topic whereas getLinkText adresses complementary functionality: it is used for Serialization.

A simple implementation of the linking service (DefaultLinkingService.java) is shipped with Xtext and is used for any grammar per default. It uses the default implementation of IScopeProvider.

An IScopeProvider is responsible for providing an IScope for a given EObject and it's EReference, for which all candidates shall be returned.

public interface IScopeProvider extends ILanguageService {
 
	/**
	 * Returns a scope for the given context.
	 *
	 * @param context - the element from which an element shall be referenced 
	 * @param reference - the reference to be filled.  
	 * @return {@link IScope} representing the inner most {@link IScope} for the passed context and reference
	 */
	public IScope getScope(EObject context, EReference reference);
}

An IScope represents an element of a hierarchy of scopes. That means that a scope can be nested within an outer scope. For instance Java has multiple kinds of scopes (object scope, type scope, etc,).

For Java one would create the scope hierarchy as commented in the following example:

// file contents scope
import static my.Constants.STATIC;
 
public class ScopeExample { // class body scope
	private Object field = STATIC;
 
	private void method(String param) { // method body scope
		String localVar = "bar";
		innerBlock: { // block scope
			String innerScopeVar = "foo";
			Object field = innerScopeVar;
			// the scope hierarchy at this point would look like so:
			//blockScope{field,innerScopeVar}->
			//methodScope{localVar,param}->
			//classScope{field}-> ('field' is overlayed)
			//fileScope{STATIC}->
			//classpathScope{'all qualified names of accessible static fields'} ->
			//NULLSCOPE{}
                        //
		}
		field.add(localVar);
	}
}

In fact the class path scope should also reflect the order of class path entries. For instance: classpathScope{stuff from bin/} -> classpathScope{stuff from foo.jar/} -> ... -> classpathScope{stuff from JRE System Library} -> NULLSCOPE{}

Default linking semantics

The default implementation for all languages, looks within the current file for an EObject of the respective type ('Entity') which has a name attribute set to 'Entity01'.

Example: Given the grammar :

...
Model : (stuff+=(Ref|Entity))*;

Ref : 'ref' referencedObject=[Entity|ID] ';';

Entity : 'entity' name=ID ';';

In the following model :

ref Entity01;
entity Entity01;

the ref would be linked to the declared entity ('entity Entity01;').

Default Imports

There is a default implementation for inter-resource referencing, which as well uses convention:

Example: Given the grammar :

...
Model : (imports+=Import)* (stuff+=(Ref|Entity))*;

Import : 'import' importURI=STRING ';';

Ref : 'ref' referencedObject=[Entity|ID] ';';

Entity : 'entity' name=ID ';';

With this grammar in place it would be possible to write something like the following:

import "model1.dsl";
import "model2.dsl";

ref Foo;
entity Bar;

--file model1.dsl

entity Stuff;

--

entity Foo;

The default scope structure is as follows:

Scope (currentFile) {
 parent : Scope (model1.dsl) {
  parent : Scope (model2.dsl) {}
 }
}

So the outer scope is asked for an Entity named 'Foo', as it does not contain such a declaration itself it's parent is asked and so on. The default implementation of IScopeProvider creates this kind of scope chain.


Fragment Provider - Playing nice with EMF URIs

Although inter-Xtext linking is not done by URIs, you may want to be able to reference your EObject from non Xtext models. In those cases URIs are used, which are made up of a part identifying the resource. Each EObject contained in a Resource can be identified by a so called 'fragment'. A fragment is a part of an EMF URI and needs to be unique per resource. The generic XMI resource shipped with EMF provides a generic path-like computation of fragments. With an XMI or other binary-like serialization it is also common and possible to use UUIDs.

However with a textual concrete syntax we want to be able to compute fragments out of the given information. We don't want to force people to use UUIDs (i.e. synthetic identifiers) or relative generic pathes (very fragile), in order to refer to EObjects. Therefore one can contribute a so called IFragmentProvider per language.

 public interface IFragmentProvider extends ILanguageService {
 
	/**
	 * Computes the local ID of the given object. 
	 * @param obj
	 *            The EObject to compute the fragment for
	 * @return the fragment, which can be an arbitrary string but must be unique
	 *         within a resource. Return null to use default implementation
	 */
	String getFragment(EObject obj);
 
	/**
	 * Locates an EObject in a resource by its fragment. 
	 * @param resource
	 * @param fragment
	 * @return the EObject 
	 */
	EObject getEObject(Resource resource, String fragment);
 }

However, the currently available default fragment provider does nothing.

UI Architecture

For the following part we will refer to a concrete example grammar in order to explain certain aspect of the UI more clearly. The used example grammar is as follows:

Model :
  "model" intAttribute = INT ( stringDescription = STRING ) ? "{" 
		( rules += AbstractRule | types += CustomTypeParserRule ) * 
  "}" 
;
 
AbstractRule:
  RuleA | RuleB
;
 
RuleA :
	 "RuleA" "(" name = ID ")" ;
 
RuleB :
	 "RuleB" "(" ruleA = [RuleA] ")" ;
 
CustomTypeParserRule returns ReferenceModel::CustomType :
	'type' name=ID;

Content Assist

The Xtext generator, amongst other things, generates the following two CA related artefacts:

  • a concrete proposal provider class named [Language]GenProposalProvider generated into the src-gen folder within the 'ui' project
  • a service framework configuration for the related CA interfaces (IProposalProvider,IContentAssistant and IContentAssistProcessor)

First we will investigate the generated [Language]GenProposalProvider which contains the following methods (for the example grammar above):

ProposalProvider

public List<? extends ICompletionProposal> completeModelIntAttribute(Assignment assignment, EObject model, String prefix, IDocument doc,int offset) {
  return Collections.singletonList(createCompletionProposal("1", offset));		
}
 
public List<? extends ICompletionProposal> completeModelStringDescription(Assignment assignment, EObject model, String prefix, IDocument doc,int offset) {
  return Collections.singletonList(createCompletionProposal("\"ModelStringDescription\"", offset));		
}
 
public List<? extends ICompletionProposal> completeModelRules(Assignment assignment, EObject model, String prefix, IDocument doc,int offset) {
  return Collections.emptyList();
}
 
public List<? extends ICompletionProposal> completeModelTypes(Assignment assignment, EObject model, String prefix, IDocument doc,int offset) {
  return Collections.emptyList();
}
 
public List<? extends ICompletionProposal> completeRuleAName(Assignment assignment, EObject model, String prefix, IDocument doc,int offset) {
  return Collections.singletonList(createCompletionProposal("RuleAName", offset));
}
 
public List<? extends ICompletionProposal> completeRuleBRuleA(Assignment assignment, EObject model, String prefix, IDocument doc,int offset) {
  return lookupCrossReference(((CrossReference)assignment.getTerminal()), model, offset);
}
 
public List<? extends ICompletionProposal> completeCustomTypeParserRuleName(Assignment assignment, EObject model, String prefix, IDocument doc,int offset) {
  return Collections.singletonList(createCompletionProposal("CustomTypeParserRuleName", offset));
}
 
public List<? extends ICompletionProposal> completeReferenceModelCustomType(RuleCall ruleCall, EObject model, String prefix,IDocument doc, int offset) {
  return Collections.emptyList();
}
 
@Override
protected String getDefaultImageFilePath() {
  return "icons/editor.gif";
}
 
@Override
protected String getPluginId() {
  return UI_PLUGIN_ID;
}

As you can see from the snippet above the generated ProposalProvider class contains a 'completion' proposal method for each assignment and rule with a custom return type. In addition to the methods declared in interface IProposalProvider, the framework tries to call this methods for assignments and rules using reflection. The signature of the generated 'completion' proposal methods are named after the following pattern.

for assignments

public List<ICompletionProposal> complete[Typename][featureName](Assignment ele, EObject model, String prefix, int offset);

for rules with a custom return type

public List<? extends ICompletionProposal> complete[ModelAlias][ReturnType](RuleCall ruleCall, EObject model, String prefix,IDocument doc, int offset);

Note that if you have generated Java classes for your domain model (meta model) you can alternatively declare the second parameter (model) using a specific type.

for assignments with a custom return type

public List<ICompletionProposal> completeCustomTypeParserRuleName(Assignment ele, ReferenceModel.CustomType model, String prefix, int offset);

Service Configuration

The configuration of the CA related part goes into the generated [Namespace]Gen[Grammar]UiConfig class and includes the following three interfaces.

  • org.eclipse.xtext.ui.common.editor.codecompletion.IProposalProvider
  • org.eclipse.jface.text.contentassist.IContentAssistant ([[2]])
  • org.eclipse.jface.text.contentassist.IContentAssistProcessor ([[3]])

TODO: describe/link where to configure a manual implementation of IProposalProvider??

Runtime Examples

model 0 >>CA<<

will execute the following call sequence to IProposalProvider implementations

  1. completeRuleCall 'STRING'
  2. completeModelStringDescription feature 'stringDescription'
  3. completeKeyword '{'
model 0 "ModelStringDescriptionSTRING" {
 >>CA<<
}
  1. completeRuleCall 'AbstractRule'
  2. completeRuleCall 'RuleA'
  3. completeKeyword 'RuleA'
  4. completeRuleCall 'RuleB'
  5. completeKeyword 'RuleB'
  6. completeModelRules feature 'rules'
  7. completeRuleCall 'CustomTypeParserRule'
  8. completeReferenceModelCustomType 'CustomTypeParserRule'
  9. completeKeyword 'type'
  10. completeModelTypes feature 'types'
  11. completeKeyword '}'
model 0 "ModelStringDescriptionSTRING" {
 type >>CA<<
}
  1. completeRuleCall 'ID'
  2. completeCustomTypeParserRuleName feature 'name'
model 0 "ModelStringDescriptionSTRING" {
	RuleA (RuleAName)
	RuleB (>>CA<<
}
  1. completeRuleBRuleA feature 'ruleA' - Which in turn invokes lookupCrossReference which delegates to the configured ILinkingCandidatesService#getLinkingCandidates to determine all available 'link' candidates.

Back to the top