Improved qualified name resolution in LSP editor.

Change-Id: Ic5e375907fcd99cb9b6b1a0d46d725ffc9bf3420
diff --git a/plugins/org.eclipse.acceleo.aql.ls/src/org/eclipse/acceleo/aql/ls/services/textdocument/AcceleoLocationLinkResolver.java b/plugins/org.eclipse.acceleo.aql.ls/src/org/eclipse/acceleo/aql/ls/services/textdocument/AcceleoLocationLinkResolver.java
index b0071b1..b6eb90b 100644
--- a/plugins/org.eclipse.acceleo.aql.ls/src/org/eclipse/acceleo/aql/ls/services/textdocument/AcceleoLocationLinkResolver.java
+++ b/plugins/org.eclipse.acceleo.aql.ls/src/org/eclipse/acceleo/aql/ls/services/textdocument/AcceleoLocationLinkResolver.java
@@ -19,6 +19,7 @@
 import org.eclipse.acceleo.ASTNode;
 import org.eclipse.acceleo.Module;
 import org.eclipse.acceleo.aql.location.AcceleoLocationLinkToAcceleo;
+import org.eclipse.acceleo.aql.location.AcceleoLocationLinkToSourceLocation;
 import org.eclipse.acceleo.aql.location.aql.AqlLocationLinkToAny;
 import org.eclipse.acceleo.aql.location.aql.AqlLocationLinkToAql;
 import org.eclipse.acceleo.aql.location.common.AbstractLocationLink;
@@ -81,6 +82,8 @@
 			locationLink = this.transform((AqlLocationLinkToAql)locationLinkToTransform);
 		} else if (locationLinkToTransform instanceof AqlLocationLinkToAny) {
 			locationLink = this.transform((AqlLocationLinkToAny)locationLinkToTransform);
+		} else if (locationLinkToTransform instanceof AcceleoLocationLinkToSourceLocation) {
+			locationLink = this.transform((AcceleoLocationLinkToSourceLocation)locationLinkToTransform);
 		} else {
 			throw new UnsupportedOperationException("Unsupported " + AbstractLocationLink.class
 					.getCanonicalName() + " implementation: " + locationLinkToTransform.toString());
@@ -111,6 +114,27 @@
 	}
 
 	/**
+	 * Transforms an {@link AcceleoLocationLinkToSourceLocation} from Acceleo into a corresponding
+	 * {@link LocationLink} for LSP4J.
+	 * 
+	 * @param acceleoLocationLinkToSourceLocation
+	 *            the (non-{@code null}) {@link AcceleoLocationLinkToSourceLocation} to transform.
+	 * @return the {@link LocationLink} corresponding to {@code acceleoLocationLinkToSourceLocation}.
+	 */
+	private LocationLink transform(AcceleoLocationLinkToSourceLocation acceleoLocationLinkToSourceLocation) {
+		ASTNode linkOrigin = acceleoLocationLinkToSourceLocation.getOrigin();
+		AcceleoTextDocument originTextDocument = getAcceleoTextDocumentContaining(linkOrigin);
+		ASTNode linkOriginEquivalent = AcceleoAstUtils.getSelfOrEquivalentOf(linkOrigin, originTextDocument
+				.getAcceleoAstResult());
+
+		Range originSelectionRange = AcceleoLanguageServerPositionUtils.getRangeOf(linkOriginEquivalent,
+				originTextDocument.getAcceleoAstResult());
+
+		ISourceLocation targetSourceLocation = acceleoLocationLinkToSourceLocation.getDestination();
+		return this.createLocationLinkFromRangeToSourceLocation(originSelectionRange, targetSourceLocation);
+	}
+
+	/**
 	 * Provides the {@link AcceleoTextDocument} containing the given Acceleo {@link ASTNode}.
 	 * 
 	 * @param astNode
@@ -336,7 +360,6 @@
 				destinationNode, destinationAcceleoAstResult);
 		Range targetRange = AcceleoLanguageServerPositionUtils.getRangeOf(
 				destinationNodeInDestinationTextDocument, destinationAcceleoAstResult);
-		// FIXME: we probably only want to select part of the target.
 		Range targetSelectionRange = AcceleoLanguageServerPositionUtils.getIdentifierRangeOf(
 				destinationNodeInDestinationTextDocument, destinationTextDocument.getAcceleoAstResult());
 
@@ -367,6 +390,36 @@
 	}
 
 	/**
+	 * Creates a {@link LocationLink} from the given {@link Range} to the given Acceleo
+	 * {@link ISourceLocation} destination.
+	 * 
+	 * @param originSelectionRange
+	 *            the (non-{@code null}) origin selection {@link Range}.
+	 * @param targetSourceLocation
+	 *            the (non-{@code null}) destination {@link ISourceLocation}.
+	 * @return the {@link LocationLink} from {@code originSelectionRange} to {@code targetSourceLocation}.
+	 */
+	private LocationLink createLocationLinkFromRangeToSourceLocation(Range originSelectionRange,
+			ISourceLocation targetSourceLocation) {
+
+		final Position rangeStart = new Position(targetSourceLocation.getRange().getStart().getLine(),
+				targetSourceLocation.getRange().getStart().getColumn());
+		final Position rangeEnd = new Position(targetSourceLocation.getRange().getEnd().getLine(),
+				targetSourceLocation.getRange().getEnd().getColumn());
+		final Range targetRange = new Range(rangeStart, rangeEnd);
+
+		final Position identifierStart = new Position(targetSourceLocation.getIdentifierRange().getStart()
+				.getLine(), targetSourceLocation.getIdentifierRange().getStart().getColumn());
+		final Position identifierEnd = new Position(targetSourceLocation.getIdentifierRange().getEnd()
+				.getLine(), targetSourceLocation.getIdentifierRange().getEnd().getColumn());
+		final Range targetSelectionRange = new Range(identifierStart, identifierEnd);
+
+		final LocationLink locationLink = new LocationLink(targetSourceLocation.getSourceURL().toString(),
+				targetRange, targetSelectionRange, originSelectionRange);
+		return locationLink;
+	}
+
+	/**
 	 * Creates a {@link LocationLink} from the given {@link Range} to the given Java {@link Class}
 	 * destination.
 	 * 
diff --git a/plugins/org.eclipse.acceleo.aql/src/org/eclipse/acceleo/aql/location/AcceleoDefinitionLocator.java b/plugins/org.eclipse.acceleo.aql/src/org/eclipse/acceleo/aql/location/AcceleoDefinitionLocator.java
index 1cc855e..48efdea 100644
--- a/plugins/org.eclipse.acceleo.aql/src/org/eclipse/acceleo/aql/location/AcceleoDefinitionLocator.java
+++ b/plugins/org.eclipse.acceleo.aql/src/org/eclipse/acceleo/aql/location/AcceleoDefinitionLocator.java
@@ -28,6 +28,7 @@
 import org.eclipse.acceleo.Variable;
 import org.eclipse.acceleo.aql.location.common.AbstractLocationLink;
 import org.eclipse.acceleo.query.runtime.namespace.IQualifiedNameQueryEnvironment;
+import org.eclipse.acceleo.query.runtime.namespace.ISourceLocation;
 import org.eclipse.acceleo.util.AcceleoSwitch;
 
 /**
@@ -103,16 +104,20 @@
 	 */
 	@Override
 	public List<AbstractLocationLink<?, ?>> caseModuleReference(ModuleReference moduleReference) {
-		final Object resolved = queryEnvironment.getLookupEngine().getResolver().resolve(moduleReference
-				.getQualifiedName());
-		if (resolved instanceof Module) {
-			return Collections.singletonList(new AcceleoLocationLinkToAcceleo(moduleReference,
-					(Module)resolved));
+		final List<AbstractLocationLink<?, ?>> res;
+
+		final ISourceLocation sourceLocation = queryEnvironment.getLookupEngine().getResolver()
+				.getSourceLocation(moduleReference.getQualifiedName());
+		if (sourceLocation != null) {
+			res = Collections.singletonList(new AcceleoLocationLinkToSourceLocation(moduleReference,
+					sourceLocation));
 		} else {
 			// Could not resolve the module reference, which means that we will not be able to find the
 			// definition of the referenced module.
-			return null;
+			res = null;
 		}
+
+		return res;
 	}
 
 	@Override
diff --git a/plugins/org.eclipse.acceleo.aql/src/org/eclipse/acceleo/aql/location/AcceleoLocationLinkToSourceLocation.java b/plugins/org.eclipse.acceleo.aql/src/org/eclipse/acceleo/aql/location/AcceleoLocationLinkToSourceLocation.java
new file mode 100644
index 0000000..56d67cc
--- /dev/null
+++ b/plugins/org.eclipse.acceleo.aql/src/org/eclipse/acceleo/aql/location/AcceleoLocationLinkToSourceLocation.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Obeo.
+ * 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
+ * 
+ * Contributors:
+ *     Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.acceleo.aql.location;
+
+import org.eclipse.acceleo.ASTNode;
+import org.eclipse.acceleo.query.runtime.namespace.ISourceLocation;
+
+/**
+ * An {@link AbstractAcceleoLocationLink} that points to an {@link ASTNode}.
+ * 
+ * @author <a href="mailto:yvan.lussaud@obeo.fr">Yvan Lussaud</a>
+ */
+public class AcceleoLocationLinkToSourceLocation extends AbstractAcceleoLocationLink<ISourceLocation> {
+
+	/**
+	 * The constructor.
+	 * 
+	 * @param fromSemanticElement
+	 *            the (non-{@code null}) origin {@link ASTNode}.
+	 * @param sourceLocation
+	 *            the (non-{@code null}) destination {@link ISourceLocation}.
+	 */
+	public AcceleoLocationLinkToSourceLocation(ASTNode fromSemanticElement, ISourceLocation sourceLocation) {
+		super(fromSemanticElement, sourceLocation);
+	}
+
+}
diff --git a/plugins/org.eclipse.acceleo.aql/src/org/eclipse/acceleo/aql/parser/ModuleLoader.java b/plugins/org.eclipse.acceleo.aql/src/org/eclipse/acceleo/aql/parser/ModuleLoader.java
index bfbab51..58f356e 100644
--- a/plugins/org.eclipse.acceleo.aql/src/org/eclipse/acceleo/aql/parser/ModuleLoader.java
+++ b/plugins/org.eclipse.acceleo.aql/src/org/eclipse/acceleo/aql/parser/ModuleLoader.java
@@ -198,4 +198,47 @@
 		return res;
 	}
 
+	@Override
+	public ISourceLocation getSourceLocation(IQualifiedNameResolver resolver, String qualifiedName) {
+		final ISourceLocation res;
+
+		final Object resolved = resolver.resolve(qualifiedName);
+		if (resolved instanceof Module) {
+			final Module module = (Module)resolved;
+			final URL sourceURL = resolver.getSourceURL(qualifiedName);
+
+			final int identifierStartLine = module.getAst().getIdentifierStartLine(module);
+			final int identifierStartColumn = module.getAst().getIdentifierStartColumn(module);
+			final int identifierStartPosition = module.getAst().getIdentifierStartPosition(module);
+			final IPosition identifierStart = new Position(identifierStartLine, identifierStartColumn,
+					identifierStartPosition);
+
+			final int identifierEndLine = module.getAst().getIdentifierEndLine(module);
+			final int identifierEndColumn = module.getAst().getIdentifierEndColumn(module);
+			final int identifierEndPosition = module.getAst().getIdentifierEndPosition(module);
+			final IPosition identifierEnd = new Position(identifierEndLine, identifierEndColumn,
+					identifierEndPosition);
+
+			final IRange identifierRange = new Range(identifierStart, identifierEnd);
+
+			final int startLine = module.getAst().getStartLine(module);
+			final int startColumn = module.getAst().getStartColumn(module);
+			final int startPosition = module.getAst().getStartPosition(module);
+			final IPosition start = new Position(startLine, startColumn, startPosition);
+
+			final int endLine = module.getAst().getEndLine(module);
+			final int endColumn = module.getAst().getEndColumn(module);
+			final int endPosition = module.getAst().getEndPosition(module);
+			final IPosition end = new Position(endLine, endColumn, endPosition);
+
+			final IRange range = new Range(start, end);
+
+			res = new SourceLocation(sourceURL, identifierRange, range);
+		} else {
+			res = null;
+		}
+
+		return res;
+	}
+
 }
diff --git a/query/plugins/org.eclipse.acceleo.query.ide.jdt/src/org/eclipse/acceleo/query/ide/jdt/EclipseJDTJavaLoader.java b/query/plugins/org.eclipse.acceleo.query.ide.jdt/src/org/eclipse/acceleo/query/ide/jdt/EclipseJDTJavaLoader.java
index 4d2ae89..0dfb04c 100644
--- a/query/plugins/org.eclipse.acceleo.query.ide.jdt/src/org/eclipse/acceleo/query/ide/jdt/EclipseJDTJavaLoader.java
+++ b/query/plugins/org.eclipse.acceleo.query.ide.jdt/src/org/eclipse/acceleo/query/ide/jdt/EclipseJDTJavaLoader.java
@@ -117,6 +117,68 @@
 		return res;
 	}
 
+	@Override
+	public ISourceLocation getSourceLocation(IQualifiedNameResolver resolver, String qualifiedName) {
+		ISourceLocation res = null;
+
+		IPosition identifierStart = new Position(0, 0, 0);
+		IPosition identifierEnd = new Position(0, 0, 0);
+		final IRange identifierRange;
+
+		IPosition start = new Position(0, 0, 0);
+		IPosition end = new Position(0, 0, 0);
+		final IRange range;
+
+		final Object resolved = resolver.resolve(qualifiedName);
+		if (resolved instanceof Class<?>) {
+			URL sourceURL = null;
+			if (resolver instanceof EclipseJDTQualifiedNameResolver) {
+				final IJavaProject project = ((EclipseJDTQualifiedNameResolver)resolver).getProject();
+				try {
+					final IType type = project.findType(((Class<?>)resolved).getCanonicalName());
+					if (type != null) {
+						type.getOpenable().open(new NullProgressMonitor());
+						sourceURL = type.getResource().getLocationURI().toURL();
+						final ISourceRange classIdentifierRange = type.getNameRange();
+						final ISourceRange sourceRange = type.getSourceRange();
+
+						final ASTParser parser = ASTParser.newParser(AST.JLS10);
+						parser.setSource(type.getCompilationUnit());
+						final CompilationUnit cu = (CompilationUnit)parser.createAST(null);
+						final int identifierStartOffset = classIdentifierRange.getOffset();
+						identifierStart = new Position(cu.getLineNumber(identifierStartOffset) - 1, cu
+								.getColumnNumber(identifierStartOffset), identifierStartOffset);
+						final int identifierEndOffset = identifierStartOffset + classIdentifierRange
+								.getLength();
+						identifierEnd = new Position(cu.getLineNumber(identifierEndOffset) - 1, cu
+								.getColumnNumber(identifierEndOffset), identifierEndOffset);
+
+						final int startOffset = sourceRange.getOffset();
+						start = new Position(cu.getLineNumber(startOffset) - 1, cu.getColumnNumber(
+								startOffset), startOffset);
+						final int endOffset = startOffset + sourceRange.getLength();
+						end = new Position(cu.getLineNumber(endOffset) - 1, cu.getColumnNumber(endOffset),
+								endOffset);
+
+						identifierRange = new Range(identifierStart, identifierEnd);
+						range = new Range(start, end);
+						res = new SourceLocation(sourceURL, identifierRange, range);
+					} else {
+						res = null;
+					}
+				} catch (JavaModelException | MalformedURLException e) {
+					// nothing to do here
+				}
+			} else {
+				// nothing to do here
+			}
+		} else {
+			res = null;
+		}
+
+		return res;
+	}
+
 	private String[] getParamterTypes(Method method) {
 		final List<String> res = new ArrayList<String>();
 
diff --git a/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/impl/namespace/ClassLoaderQualifiedNameResolver.java b/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/impl/namespace/ClassLoaderQualifiedNameResolver.java
index aed64ae..fa162e2 100644
--- a/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/impl/namespace/ClassLoaderQualifiedNameResolver.java
+++ b/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/impl/namespace/ClassLoaderQualifiedNameResolver.java
@@ -162,6 +162,20 @@
 		return res;
 	}
 
+	@Override
+	public ISourceLocation getSourceLocation(String qualifiedName) {
+		ISourceLocation res = null;
+
+		for (ILoader loader : loaders) {
+			res = loader.getSourceLocation(this, qualifiedName);
+			if (res != null) {
+				break;
+			}
+		}
+
+		return res;
+	}
+
 	/**
 	 * Loads the {@link Object} from the given qualified name.
 	 * 
diff --git a/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/impl/namespace/JavaLoader.java b/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/impl/namespace/JavaLoader.java
index d076f09..caba2c2 100644
--- a/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/impl/namespace/JavaLoader.java
+++ b/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/impl/namespace/JavaLoader.java
@@ -68,4 +68,9 @@
 		return null;
 	}
 
+	@Override
+	public ISourceLocation getSourceLocation(IQualifiedNameResolver resolver, String qualifiedName) {
+		return null;
+	}
+
 }
diff --git a/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/namespace/ILoader.java b/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/namespace/ILoader.java
index 212dc75..b5eae37 100644
--- a/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/namespace/ILoader.java
+++ b/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/namespace/ILoader.java
@@ -114,4 +114,15 @@
 	 */
 	ISourceLocation getSourceLocation(IQualifiedNameResolver resolver, IService<?> service);
 
+	/**
+	 * Gets the {@link ISourceLocation} for the given qualified name.
+	 * 
+	 * @param resolver
+	 *            the {@link IQualifiedNameResolver}
+	 * @param qualifiedName
+	 *            the qualified name
+	 * @return the {@link ISourceLocation} for the given qualified name if any, <code>null</code> otherwise
+	 */
+	ISourceLocation getSourceLocation(IQualifiedNameResolver resolver, String qualifiedName);
+
 }
diff --git a/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/namespace/IQualifiedNameResolver.java b/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/namespace/IQualifiedNameResolver.java
index f62da72..36827b6 100644
--- a/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/namespace/IQualifiedNameResolver.java
+++ b/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/namespace/IQualifiedNameResolver.java
@@ -61,6 +61,15 @@
 	ISourceLocation getSourceLocation(IService<?> service);
 
 	/**
+	 * Gets the {@link ISourceLocation} for the given qualified name.
+	 * 
+	 * @param qualifiedName
+	 *            the qualified name
+	 * @return the {@link ISourceLocation} for the given qualified name if any, <code>null</code> otherwise
+	 */
+	ISourceLocation getSourceLocation(String qualifiedName);
+
+	/**
 	 * Clears the cache for the given {@link Set} of qualified names.
 	 * 
 	 * @param qualifiedNames