Introduced content assist conflict detection and prefix matching for web context

Signed-off-by: Miro Spönemann <miro.spoenemann@typefox.io>
This commit is contained in:
Miro Spönemann 2016-04-29 08:53:06 +02:00
parent 324180d6c8
commit 707511f4c2
15 changed files with 475 additions and 84 deletions

View file

@ -8,4 +8,20 @@
</message_arguments>
</filter>
</resource>
<resource path="xtend-gen/org/eclipse/xtext/ide/editor/contentassist/IdeContentProposalProvider.java" type="org.eclipse.xtext.ide.editor.contentassist.IdeContentProposalProvider">
<filter id="338792546">
<message_arguments>
<message_argument value="org.eclipse.xtext.ide.editor.contentassist.IdeContentProposalProvider"/>
<message_argument value="isAcceptable(ContentAssistContext)"/>
</message_arguments>
</filter>
</resource>
<resource path="xtend-gen/org/eclipse/xtext/ide/editor/contentassist/IdeCrossrefProposalProvider.java" type="org.eclipse.xtext.ide.editor.contentassist.IdeCrossrefProposalProvider">
<filter id="338792546">
<message_arguments>
<message_argument value="org.eclipse.xtext.ide.editor.contentassist.IdeCrossrefProposalProvider"/>
<message_argument value="matchesPrefix(IEObjectDescription, QualifiedName)"/>
</message_arguments>
</filter>
</resource>
</component>

View file

@ -30,6 +30,8 @@ import org.eclipse.xtext.util.TextRegion
*/
abstract class AbstractIdeTemplateProposalProvider {
@Inject IdeContentProposalCreator proposalCreator
@Inject IdeContentProposalPriorities proposalPriorities
/** Placeholder for a variable (edit position) in a template. */
@ -66,7 +68,7 @@ abstract class AbstractIdeTemplateProposalProvider {
}
protected def boolean canAcceptProposal(ContentAssistEntry entry, ContentAssistContext context) {
entry.proposal.startsWith(context.prefix)
proposalCreator.isValidProposal(entry.proposal, entry.prefix, context)
}
protected def ContentAssistEntry createProposal(StringConcatenationClient template,

View file

@ -0,0 +1,69 @@
/*******************************************************************************
* Copyright (c) 2016 TypeFox GmbH (http://www.typefox.io) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.xtext.ide.editor.contentassist
import com.google.inject.Inject
import org.eclipse.xtend.lib.annotations.Accessors
import org.eclipse.xtext.naming.IQualifiedNameConverter
import org.eclipse.xtext.util.Strings
/**
* Prefix matcher for fully qualified names.
*
* @since 2.10
* @noreference
*/
class FQNPrefixMatcher implements IPrefixMatcher {
@Accessors
@Inject IPrefixMatcher.IgnoreCase delegate
@Inject IQualifiedNameConverter qualifiedNameConverter
override boolean isCandidateMatchingPrefix(String name, String prefix) {
if (delegate.isCandidateMatchingPrefix(name, prefix))
return true
val delimiter = getDelimiter
// Assume a FQN if delimiter is present
if (!delimiter.nullOrEmpty && name.indexOf(delimiter) >= 0) {
if (prefix.indexOf(delimiter) < 0) {
// Prefix is without delimiter - either namespace or last segment
val lastSegment = getLastSegment(name, delimiter)
if (lastSegment !== null && delegate.isCandidateMatchingPrefix(lastSegment, prefix))
return true
} else {
val splitPrefix = Strings.split(prefix, delimiter)
if (splitPrefix.empty)
return false
val splitName = Strings.split(name, delimiter)
if (splitName.size < splitPrefix.size)
return false
for (var i = 0; i < splitPrefix.size; i++) {
if (!delegate.isCandidateMatchingPrefix(splitName.get(i), splitPrefix.get(i)))
return false
}
return true
}
}
return false
}
def protected String getLastSegment(String fqn, String delimiter) {
val lastDelimIndex = fqn.lastIndexOf(delimiter)
if (lastDelimIndex >= 0 && lastDelimIndex + delimiter.length < fqn.length)
return fqn.substring(lastDelimIndex + delimiter.length)
}
def String getDelimiter() {
if (qualifiedNameConverter instanceof IQualifiedNameConverter.DefaultImpl)
return qualifiedNameConverter.delimiter
else
return '.'
}
}

View file

@ -14,6 +14,9 @@ import org.eclipse.xtext.ide.editor.contentassist.ContentAssistEntry
*/
interface IIdeContentProposalAcceptor {
/**
* Handle the given content assist entry. The entry may be {@code null}.
*/
def void accept(ContentAssistEntry entry, int priority)
def boolean canAcceptMoreProposals()

View file

@ -0,0 +1,35 @@
/*******************************************************************************
* Copyright (c) 2016 TypeFox GmbH (http://www.typefox.io) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.xtext.ide.editor.contentassist
import com.google.inject.ImplementedBy
/**
* Prefix matchers are used to reject content assist proposals that do not match the prefix at
* the current cursor position.
*
* @since 2.10
* @noreference
*/
@ImplementedBy(IPrefixMatcher.IgnoreCase)
interface IPrefixMatcher {
def boolean isCandidateMatchingPrefix(String name, String prefix)
/**
* Default prefix matcher that compares the prefix of the candidate ignoring case.
*/
static class IgnoreCase implements IPrefixMatcher {
override boolean isCandidateMatchingPrefix(String name, String prefix) {
return name.regionMatches(true, 0, prefix, 0, prefix.length)
}
}
}

View file

@ -0,0 +1,44 @@
/*******************************************************************************
* Copyright (c) 2009 itemis AG (http://www.itemis.eu) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.xtext.ide.editor.contentassist;
import com.google.inject.ImplementedBy;
/**
* <p>The {@link IProposalConflictHelper} is used to determine whether a
* possible content proposal is in conflict with the previous input.</p>
* <p>Implementors should consider to extend the
* {@link org.eclipse.xtext.ide.editor.contentassist.antlr.AntlrProposalConflictHelper AntlrProposalConflictHelper}.
* </p>
*
* @author Sebastian Zarnekow - Initial contribution and API
* @author Miro Sp&ouml;nemann - Copied from org.eclipse.xtext.ui.editor.contentassist
* @since 2.10
* @noreference
*/
@ImplementedBy(IProposalConflictHelper.NullHelper.class)
public interface IProposalConflictHelper {
/**
* Returns <code>false</code> if the proposal would corrupt the previous
* input.
* @param proposal a possible proposal string. Is never <code>null</code>.
* @param context the current content assist context. Is never <code>null</code>.
* @return <code>false</code> if the proposal would corrupt the current input.
*/
boolean existsConflict(String proposal, ContentAssistContext context);
public static class NullHelper implements IProposalConflictHelper {
@Override
public boolean existsConflict(String proposal, ContentAssistContext context) {
return false;
}
}
}

View file

@ -0,0 +1,61 @@
/*******************************************************************************
* Copyright (c) 2016 TypeFox GmbH (http://www.typefox.io) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.xtext.ide.editor.contentassist
import com.google.inject.Inject
/**
* Factory for content assist entries. Whenever possible, you should use this creator instead of building entries
* directly, since prefix matching and conflict handling is done here.
*
* @since 2.10
* @noreference
*/
class IdeContentProposalCreator {
@Inject IPrefixMatcher prefixMatcher
@Inject IProposalConflictHelper conflictHelper
/**
* Returns an entry with the given proposal and the prefix from the context, or null if the proposal is not valid.
*/
def ContentAssistEntry createProposal(String proposal, ContentAssistContext context) {
createProposal(proposal, context.prefix, context, null)
}
/**
* Returns an entry with the given proposal and the prefix from the context, or null if the proposal is not valid.
* If it is valid, the initializer function is applied to it.
*/
def ContentAssistEntry createProposal(String proposal, ContentAssistContext context, (ContentAssistEntry)=>void init) {
createProposal(proposal, context.prefix, context, init)
}
/**
* Returns an entry with the given proposal and prefix, or null if the proposal is not valid.
* If it is valid, the initializer function is applied to it.
*/
def ContentAssistEntry createProposal(String proposal, String prefix, ContentAssistContext context,
(ContentAssistEntry)=>void init) {
if (isValidProposal(proposal, prefix, context)) {
val result = new ContentAssistEntry
result.proposal = proposal
result.prefix = prefix
if (init !== null)
init.apply(result)
return result
}
}
def boolean isValidProposal(String proposal, String prefix, ContentAssistContext context) {
!proposal.nullOrEmpty && prefixMatcher.isCandidateMatchingPrefix(proposal, prefix)
&& !conflictHelper.existsConflict(proposal, context)
}
}

View file

@ -28,6 +28,8 @@ class IdeContentProposalPriorities {
int keywordPriority = 300
protected def adjustPriority(ContentAssistEntry entry, int priority) {
if (entry === null)
return priority
var adjustedPriority = priority
if (!Character.isLetter(entry.proposal.charAt(0)))
adjustedPriority -= 30

View file

@ -43,6 +43,9 @@ class IdeContentProposalProvider {
@Accessors(PROTECTED_GETTER)
@Inject IdeCrossrefProposalProvider crossrefProposalProvider
@Accessors(PROTECTED_GETTER)
@Inject IdeContentProposalCreator proposalCreator
@Accessors(PROTECTED_GETTER)
@Inject IdeContentProposalPriorities proposalPriorities
@ -63,23 +66,7 @@ class IdeContentProposalProvider {
}
protected def Iterable<ContentAssistContext> getFilteredContexts(Collection<ContentAssistContext> contexts) {
var ContentAssistContext selectedContext
for (context : contexts) {
if (selectedContext === null || context.acceptable
&& (context.prefix.length > selectedContext.prefix.length || !selectedContext.acceptable)) {
selectedContext = context
}
}
val finalSelectedContext = selectedContext
return contexts.filter[ context |
context === finalSelectedContext
|| context.prefix == finalSelectedContext.prefix && context.acceptable
]
}
protected def isAcceptable(ContentAssistContext context) {
val prefix = context.prefix
return prefix.empty || Character.isJavaIdentifierPart(prefix.charAt(prefix.length - 1))
return contexts
}
protected def dispatch void createProposals(AbstractElement element, ContentAssistContext context,
@ -95,15 +82,16 @@ class IdeContentProposalProvider {
} else if (terminal instanceof RuleCall) {
val rule = terminal.rule
if (rule instanceof TerminalRule && context.prefix.empty) {
val entry = new ContentAssistEntry => [
prefix = context.prefix
if (rule.name == 'STRING') {
proposal = '"' + assignment.feature + '"'
val proposal =
if (rule.name == 'STRING')
'"' + assignment.feature + '"'
else
assignment.feature
val entry = proposalCreator.createProposal(proposal, context) [
if (rule.name == 'STRING')
editPositions += new TextRegion(context.offset + 1, proposal.length - 2)
} else {
proposal = assignment.feature
else
editPositions += new TextRegion(context.offset, proposal.length)
}
description = rule.name
]
acceptor.accept(entry, proposalPriorities.getDefaultPriority(entry))
@ -114,17 +102,13 @@ class IdeContentProposalProvider {
protected def dispatch void createProposals(Keyword keyword, ContentAssistContext context,
IIdeContentProposalAcceptor acceptor) {
if (filterKeyword(keyword, context)) {
val entry = new ContentAssistEntry => [
prefix = context.prefix
proposal = keyword.value
]
val entry = proposalCreator.createProposal(keyword.value, context)
acceptor.accept(entry, proposalPriorities.getKeywordPriority(keyword.value, entry))
}
}
protected def boolean filterKeyword(Keyword keyword, ContentAssistContext context) {
keyword.value.regionMatches(true, 0, context.prefix, 0, context.prefix.length)
&& keyword.value.length > context.prefix.length
return true
}
protected def dispatch void createProposals(RuleCall ruleCall, ContentAssistContext context,

View file

@ -12,10 +12,7 @@ import com.google.inject.Inject
import org.apache.log4j.Logger
import org.eclipse.xtend.lib.annotations.Accessors
import org.eclipse.xtext.CrossReference
import org.eclipse.xtext.ide.editor.contentassist.ContentAssistContext
import org.eclipse.xtext.ide.editor.contentassist.ContentAssistEntry
import org.eclipse.xtext.naming.IQualifiedNameConverter
import org.eclipse.xtext.naming.QualifiedName
import org.eclipse.xtext.resource.IEObjectDescription
import org.eclipse.xtext.scoping.IScope
@ -30,6 +27,9 @@ class IdeCrossrefProposalProvider {
@Accessors(PROTECTED_GETTER)
@Inject IQualifiedNameConverter qualifiedNameConverter
@Accessors(PROTECTED_GETTER)
@Inject IdeContentProposalCreator proposalCreator
@Inject IdeContentProposalPriorities proposalPriorities
def void lookupCrossReference(IScope scope, CrossReference crossReference, ContentAssistContext context,
@ -50,34 +50,12 @@ class IdeCrossrefProposalProvider {
}
protected def queryScope(IScope scope, CrossReference crossReference, ContentAssistContext context) {
if (context.prefix.empty) {
return scope.allElements
}
val prefix = qualifiedNameConverter.toQualifiedName(context.prefix)
scope.allElements.filter[matchesPrefix(prefix)]
}
protected def matchesPrefix(IEObjectDescription candidate, QualifiedName prefix) {
val name = candidate.name
val count = prefix.segmentCount
if (count > name.segmentCount)
return false
for (var i = 0; i < count; i++) {
val nameSegment = name.getSegment(i)
val prefixSegment = prefix.getSegment(i)
if (i < count - 1 && nameSegment != prefixSegment
|| i == count - 1 && !nameSegment.regionMatches(true, 0, prefixSegment, 0, prefixSegment.length)) {
return false
}
}
return true
return scope.allElements
}
protected def ContentAssistEntry createProposal(IEObjectDescription candidate, CrossReference crossRef, ContentAssistContext context) {
return new ContentAssistEntry => [
proposalCreator.createProposal(qualifiedNameConverter.toString(candidate.name), context) [
source = candidate
prefix = context.prefix
proposal = qualifiedNameConverter.toString(candidate.name)
description = candidate.getEClass?.name
]
}

View file

@ -0,0 +1,61 @@
/*******************************************************************************
* Copyright (c) 2009 itemis AG (http://www.itemis.eu) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.xtext.ide.editor.contentassist;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.util.ITextRegion;
import org.eclipse.xtext.util.Strings;
/**
* <p>Abstract base implementation of the {@link IProposalConflictHelper} that
* deals with the node model to extract the previous sibling of the input source.</p>
* <p>Implementors have to provide the semantics of {@link #existsConflict(String, String, ContentAssistContext)}.
* They should consider to extend the
* {@link org.eclipse.xtext.ide.editor.contentassist.antlr.AntlrProposalConflictHelper AntlrProposalConflictHelper}.
* </p>
*
* @author Sebastian Zarnekow - Initial contribution and API
* @author Miro Sp&ouml;nemann - Copied from org.eclipse.xtext.ui.editor.contentassist
* @since 2.10
* @noreference
*/
public abstract class ProposalConflictHelper implements IProposalConflictHelper {
@Override
public boolean existsConflict(String proposal, ContentAssistContext context) {
// hidden node between lastCompleteNode and currentNode?
INode lastCompleteNode = context.getLastCompleteNode();
ITextRegion replaceRegion = context.getReplaceRegion();
int nodeEnd = lastCompleteNode.getEndOffset();
if (nodeEnd < replaceRegion.getOffset())
return false;
return existsConflict(lastCompleteNode, replaceRegion.getOffset(), proposal, context);
}
/**
* Returns <code>false</code> if the proposal would corrupt the previous
* input.
* @param lastCompleteText the previous sibling in the input source. Is never <code>null</code>
* but may be empty. However, the implementation of {@link #existsConflict(INode, int, String, ContentAssistContext)}
* will not pass empty strings by default but return <code>false</code> instead.
* @param proposal a possible proposal string. Is never <code>null</code>.
* @param context the current content assist context. Is never <code>null</code>.
* @return <code>false</code> if the proposal would corrupt the current input.
*/
public abstract boolean existsConflict(String lastCompleteText, String proposal, ContentAssistContext context);
public boolean existsConflict(INode lastCompleteNode, int offset, String proposal, ContentAssistContext context) {
String lastCompleteText = lastCompleteNode.getText();
lastCompleteText = lastCompleteText.substring(0, offset - lastCompleteNode.getTotalOffset());
if (Strings.isEmpty(lastCompleteText))
return false;
return existsConflict(lastCompleteText, proposal, context);
}
}

View file

@ -0,0 +1,123 @@
/*******************************************************************************
* Copyright (c) 2009 itemis AG (http://www.itemis.eu) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.xtext.ide.editor.contentassist.antlr;
import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CharStream;
import org.antlr.runtime.Token;
import org.antlr.runtime.TokenSource;
import org.eclipse.xtext.ide.editor.contentassist.ContentAssistContext;
import org.eclipse.xtext.ide.editor.contentassist.ProposalConflictHelper;
import org.eclipse.xtext.parser.antlr.Lexer;
import org.eclipse.xtext.parser.antlr.LexerBindings;
import com.google.inject.Inject;
import com.google.inject.name.Named;
/**
* <p>An implementation that relies on the lexer to detect proposals that conflict
* with the input in the document.</p>
* <p>A proposal is considered to be conflicting if the lexer would not produce
* two distinct tokens for the previous sibling and the proposal itself but consume
* parts of the proposal as part of the first token.
* Example:</p>
* <pre>
* === Grammar:
* MyParserRule: name=ID other=[MyParserRule];
* === Input:
* MyId|
* </pre>
* <p>where <code>|</code> denotes the cursor position. A valid follow
* token for the parser is the ID of the cross reference. However, since
* <code>MyIdSomethingElse</code> would be consumed as a single ID by the lexer,
* the proposal <code>SomethingElse</code> is invalid.</p>
*
* @author Sebastian Zarnekow - Initial contribution and API
* @author Miro Sp&ouml;nemann - Copied from org.eclipse.xtext.ui.editor.contentassist.antlr
* @since 2.10
* @noreference
*/
public class AntlrProposalConflictHelper extends ProposalConflictHelper {
@Inject
@Named(LexerBindings.RUNTIME)
private Lexer proposalLexer;
@Inject
@Named(LexerBindings.RUNTIME)
private Lexer lastCompleteLexer;
@Inject
@Named(LexerBindings.RUNTIME)
private Lexer combinedLexer;
@Override
public boolean existsConflict(String lastCompleteText, String proposal, ContentAssistContext context) {
initTokenSources(lastCompleteText, proposal, context);
if (!equalTokenSequence(getLastCompleteLexer(), getCombinedLexer()))
return true;
if (!equalTokenSequence(getProposalLexer(), getCombinedLexer()))
return true;
Token lastToken = getProposalLexer().nextToken();
if (!lastToken.equals(Token.EOF_TOKEN))
return true;
return false;
}
protected boolean equalTokenSequence(TokenSource first, TokenSource second) {
Token token = null;
while (!(token = first.nextToken()).equals(Token.EOF_TOKEN)) {
Token otherToken = second.nextToken();
if (otherToken.equals(Token.EOF_TOKEN)) {
return false;
}
if (!token.getText().equals(otherToken.getText())) {
return false;
}
}
return true;
}
protected void initTokenSources(String lastCompleteText, String proposal, ContentAssistContext context) {
String combinedText = lastCompleteText.concat(proposal);
initTokenSource(combinedText, getCombinedLexer(), context);
initTokenSource(lastCompleteText, getLastCompleteLexer(), context);
initTokenSource(proposal, getProposalLexer(), context);
}
protected void initTokenSource(String text, TokenSource tokenSource, ContentAssistContext context) {
Lexer lexer = (Lexer) tokenSource;
CharStream stream = new ANTLRStringStream(text);
lexer.setCharStream(stream);
}
public TokenSource getProposalLexer() {
return proposalLexer;
}
public void setProposalLexer(Lexer proposalLexer) {
this.proposalLexer = proposalLexer;
}
public TokenSource getCombinedLexer() {
return combinedLexer;
}
public void setCombinedLexer(Lexer combinedLexer) {
this.combinedLexer = combinedLexer;
}
public void setLastCompleteLexer(Lexer lastCompleteLexer) {
this.lastCompleteLexer = lastCompleteLexer;
}
public TokenSource getLastCompleteLexer() {
return lastCompleteLexer;
}
}

View file

@ -20,12 +20,18 @@ class QualifiedNamesFragment2 extends AbstractXtextGeneratorFragment {
new GuiceModuleAccess.BindingFactory()
.addTypeToType(IQualifiedNameProvider.typeRef, DefaultDeclarativeQualifiedNameProvider.typeRef)
.contributeTo(language.runtimeGenModule)
new GuiceModuleAccess.BindingFactory()
.addTypeToType('org.eclipse.xtext.ui.editor.contentassist.PrefixMatcher'.typeRef,
'org.eclipse.xtext.ui.editor.contentassist.FQNPrefixMatcher'.typeRef)
.addTypeToType('org.eclipse.xtext.ui.refactoring.IDependentElementsCalculator'.typeRef,
'org.eclipse.xtext.ui.refactoring.impl.DefaultDependentElementsCalculator'.typeRef)
.contributeTo(language.eclipsePluginGenModule)
new GuiceModuleAccess.BindingFactory()
.addTypeToType('org.eclipse.xtext.ide.editor.contentassist.IPrefixMatcher'.typeRef,
'org.eclipse.xtext.ide.editor.contentassist.FQNPrefixMatcher'.typeRef)
.contributeTo(language.webGenModule)
}
}

View file

@ -95,20 +95,21 @@ class XtextAntlrGeneratorFragment2 extends AbstractAntlrGeneratorFragment2 {
new KeywordHelper(grammar, options.ignoreCase, grammarUtil)
new CombinedGrammarMarker(isCombinedGrammar).attachToEmfObject(grammar)
if (debugGrammar)
generateDebugGrammar
generateProductionGrammar
generateDebugGrammar()
generateProductionGrammar()
if (projectConfig.genericIde.srcGen != null)
generateContentAssistGrammar
generateContentAssistGrammar()
generateProductionParser.writeTo(projectConfig.runtime.srcGen)
generateAntlrTokenFileProvider.writeTo(projectConfig.runtime.srcGen)
generateContentAssistParser.writeTo(projectConfig.genericIde.srcGen)
if (grammar.allTerminalRules().exists[ isSyntheticTerminalRule ]) {
generateProductionTokenSource.writeTo(projectConfig.runtime.src)
generateContentAssistTokenSource.writeTo(projectConfig.genericIde.src)
generateProductionParser().writeTo(projectConfig.runtime.srcGen)
generateAntlrTokenFileProvider().writeTo(projectConfig.runtime.srcGen)
generateContentAssistParser().writeTo(projectConfig.genericIde.srcGen)
if (grammar.allTerminalRules.exists[ isSyntheticTerminalRule ]) {
generateProductionTokenSource().writeTo(projectConfig.runtime.src)
generateContentAssistTokenSource().writeTo(projectConfig.genericIde.src)
}
addRuntimeBindingsAndImports
addUiBindingsAndImports
addRuntimeBindingsAndImports()
addUiBindingsAndImports()
addWebBindings()
}
def void setLookaheadThreshold(String lookaheadThreshold) {
@ -429,7 +430,7 @@ class XtextAntlrGeneratorFragment2 extends AbstractAntlrGeneratorFragment2 {
issues.addError("Backtracking lexer and ignorecase cannot be combined for now.")
}
def addRuntimeBindingsAndImports() {
def protected addRuntimeBindingsAndImports() {
val extension naming = productionNaming
if (projectConfig.runtime.manifest !== null) {
projectConfig.runtime.manifest=>[
@ -466,7 +467,7 @@ class XtextAntlrGeneratorFragment2 extends AbstractAntlrGeneratorFragment2 {
}
def addUiBindingsAndImports() {
def protected addUiBindingsAndImports() {
val extension naming = contentAssistNaming
val caLexerClass = grammar.lexerClass
@ -521,5 +522,24 @@ class XtextAntlrGeneratorFragment2 extends AbstractAntlrGeneratorFragment2 {
}
uiBindings.contributeTo(language.eclipsePluginGenModule)
}
def protected addWebBindings() {
val extension naming = contentAssistNaming
val webBindings = new GuiceModuleAccess.BindingFactory()
.addTypeToType(
"org.eclipse.xtext.ide.editor.contentassist.IProposalConflictHelper".typeRef,
"org.eclipse.xtext.ide.editor.contentassist.antlr.AntlrProposalConflictHelper".typeRef
)
.addConfiguredBinding("ContentAssistLexer", '''
binder.bind(«grammar.lexerSuperClass».class)
.annotatedWith(«Names».named(«"org.eclipse.xtext.ide.LexerIdeBindings".typeRef».CONTENT_ASSIST))
.to(«grammar.lexerClass».class);
''')
.addTypeToType(
"org.eclipse.xtext.ide.editor.contentassist.antlr.IContentAssistParser".typeRef,
grammar.parserClass
)
webBindings.contributeTo(language.webGenModule)
}
}

View file

@ -11,7 +11,6 @@ import com.google.common.collect.LinkedHashMultimap
import com.google.common.collect.Multimap
import com.google.inject.Inject
import com.google.inject.Provider
import com.google.inject.name.Names
import java.util.ArrayList
import java.util.Collection
import java.util.Collections
@ -23,7 +22,6 @@ import java.util.concurrent.Executors
import java.util.regex.Pattern
import org.eclipse.emf.mwe2.runtime.Mandatory
import org.eclipse.xtend.lib.annotations.Accessors
import org.eclipse.xtend2.lib.StringConcatenationClient
import org.eclipse.xtext.Grammar
import org.eclipse.xtext.GrammarUtil
import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment
@ -31,9 +29,7 @@ import org.eclipse.xtext.xtext.generator.CodeConfig
import org.eclipse.xtext.xtext.generator.Issues
import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming
import org.eclipse.xtext.xtext.generator.model.FileAccessFactory
import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess
import org.eclipse.xtext.xtext.generator.model.TypeReference
import org.eclipse.xtext.xtext.generator.parser.antlr.ContentAssistGrammarNaming
import org.eclipse.xtext.xtext.generator.util.BooleanGeneratorOption
import org.eclipse.xtext.xtext.generator.util.GeneratorOption
import org.eclipse.xtext.xtext.generator.xbase.XbaseUsageDetector
@ -61,7 +57,6 @@ class WebIntegrationFragment extends AbstractXtextGeneratorFragment {
@Inject FileAccessFactory fileAccessFactory
@Inject CodeConfig codeConfig
@Inject extension XtextGeneratorNaming
@Inject ContentAssistGrammarNaming caNaming
@Inject extension XbaseUsageDetector
val enabledPatterns = new HashSet<String>
@ -249,14 +244,6 @@ class WebIntegrationFragment extends AbstractXtextGeneratorFragment {
if (generateWebXml.get && projectConfig.web.assets !== null) {
generateWebXml()
}
val StringConcatenationClient lexerStatement =
'''binder.bind(«'org.eclipse.xtext.ide.editor.contentassist.antlr.internal.Lexer'.typeRef».class).annotatedWith(«Names».named(«'org.eclipse.xtext.ide.LexerIdeBindings'.typeRef».CONTENT_ASSIST)).to(«caNaming.getLexerClass(grammar)».class);'''
new GuiceModuleAccess.BindingFactory()
.addConfiguredBinding('ContentAssistLexer', lexerStatement)
.addTypeToType('org.eclipse.xtext.ide.editor.contentassist.antlr.IContentAssistParser'.typeRef,
caNaming.getParserClass(grammar))
.contributeTo(language.webGenModule)
}
static val DELIMITERS_PATTERN = '''[\\s.:;,!?+\\-*/&|<>()[\\]{}]'''