From 82f05a38587a1171510d2a86aec93ea4d0f7f040 Mon Sep 17 00:00:00 2001 From: Yannick Daveluy Date: Sun, 7 Apr 2024 16:06:06 +0200 Subject: [PATCH] support for nested projects in VSCode --- .../XsmpcatIdeContentProposalProvider.xtend | 21 +++++++ .../xsmp/ide/hover/XsmpHoverService.java | 17 ++++++ .../xsmp/ide/hover/XsmpcatKeywordHovers.xtend | 28 ++++++++- .../ide/hover/XsmpprojectKeywordHovers.xtend | 8 ++- .../xsmp/ide/labeling/XsmpLabelProvider.java | 7 +-- .../XsmpDocumentSymbolDetailsProvider.java | 12 +++- .../xsmp/ide/workspace/XsmpProjectConfig.java | 33 +++++++++- .../xsmp/server/XsmpProjectConfigFactory.java | 60 +++++++++++++------ .../containers/XsmpProjectsStateHelper.java | 4 +- .../ui/labeling/XsmpprojectLabelProvider.java | 8 +-- .../ui/validation/XsmpprojectUIValidator.java | 18 ++---- .../workspace/XsmpEclipseProjectConfig.java | 16 ++++- org.eclipse.xsmp/model/xsmp.ecore | 4 +- org.eclipse.xsmp/model/xsmp.genmodel | 1 + .../src/org/eclipse/xsmp/Xsmpcat.xtext | 2 +- .../src/org/eclipse/xsmp/Xsmpproject.xtext | 7 ++- .../formatting2/XsmpprojectFormatter.java | 4 ++ .../model/xsmp/impl/SourcePathImplCustom.java | 31 ++++++++++ .../xsmp/validation/XsmpcatValidator.java | 16 +++-- .../xsmp/validation/XsmpprojectValidator.java | 36 +++++++++-- .../xsmp/workspace/IXsmpProjectConfig.java | 12 ++++ .../syntaxes/xsmpcat.tmLanguage.json | 8 +-- .../syntaxes/xsmpproject.tmLanguage.json | 2 +- 23 files changed, 284 insertions(+), 71 deletions(-) create mode 100644 org.eclipse.xsmp/src/org/eclipse/xsmp/model/xsmp/impl/SourcePathImplCustom.java diff --git a/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/contentassist/XsmpcatIdeContentProposalProvider.xtend b/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/contentassist/XsmpcatIdeContentProposalProvider.xtend index d6ad0087..f10464e0 100644 --- a/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/contentassist/XsmpcatIdeContentProposalProvider.xtend +++ b/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/contentassist/XsmpcatIdeContentProposalProvider.xtend @@ -220,6 +220,27 @@ class XsmpcatIdeContentProposalProvider extends XsmpIdeContentProposalProvider { array ${1:name} = ${2|«getCrossReferences(context.currentModel, XsmpPackage.Literals.ARRAY__ITEM_TYPE)»|}[$0] ''', "Create an Array Type", context), snippetPriority); } + def protected complete_nativeType(ContentAssistContext context, IIdeContentProposalAcceptor acceptor) { + acceptor.accept(getProposalCreator().createSnippet(''' + /** + * @type native_type + * @location native_location + * @namespace native_namespace + * @uuid «generateUuid» + */ + class ${1:name} + ''', "Create a Native type", context), snippetPriority); + } + def protected complete_attributeType(ContentAssistContext context, IIdeContentProposalAcceptor acceptor) { + acceptor.accept(getProposalCreator().createSnippet(''' + /** + * // @allowMultiple + * @usage ... + * @uuid «generateUuid» + */ + attribute ${1|«getCrossReferences(context.currentModel, XsmpPackage.Literals.ATTRIBUTE_TYPE__TYPE)»|} ${2:name} = $0 + ''', "Create an Attribute type", context), snippetPriority); + } def private String getCrossReferences(EObject eObject, EReference eReference) { val scope = scopeProvider.getScope(eObject, eReference) diff --git a/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/hover/XsmpHoverService.java b/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/hover/XsmpHoverService.java index 5c73d1da..595c5e70 100644 --- a/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/hover/XsmpHoverService.java +++ b/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/hover/XsmpHoverService.java @@ -11,12 +11,15 @@ package org.eclipse.xsmp.ide.hover; import org.eclipse.emf.ecore.EObject; +import org.eclipse.lsp4j.Hover; +import org.eclipse.lsp4j.HoverParams; import org.eclipse.xsmp.model.xsmp.NamedElement; import org.eclipse.xtext.Keyword; import org.eclipse.xtext.ide.server.Document; import org.eclipse.xtext.ide.server.hover.HoverContext; import org.eclipse.xtext.ide.server.hover.HoverService; import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.util.CancelIndicator; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -69,4 +72,18 @@ protected HoverContext createContext(Document document, XtextResource resource, return super.createContext(document, resource, offset); } + @Override + public Hover hover(Document document, XtextResource resource, HoverParams params, + CancelIndicator cancelIndicator) + { + try + { + return super.hover(document, resource, params, cancelIndicator); + } + catch (final IndexOutOfBoundsException e) + { + return null; + } + } + } diff --git a/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/hover/XsmpcatKeywordHovers.xtend b/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/hover/XsmpcatKeywordHovers.xtend index d1300c3b..eb75095a 100644 --- a/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/hover/XsmpcatKeywordHovers.xtend +++ b/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/hover/XsmpcatKeywordHovers.xtend @@ -142,7 +142,7 @@ class XsmpcatKeywordHovers implements IKeywordHovers {
Optionally, the PrimitiveType used to encode the integer value may be specified (one of Int8, Int16, Int32, Int64, UIn8, UInt16, UInt32, UInt64, where the default is Int32).

''' case ga.namespaceMemberAccess.floatKeyword_3_10_2: ''' -

«visibility()» «kw("float")» name [«kw("extends")» (Float32|Float64)] [«kw("in")» (*|decimalExpression) ... (*|decimalExpression)]

+

«visibility()» «kw("float")» name [«kw("extends")» (Float32|Float64)] [«kw("in")» (*|decimalExpression) (...|<..|..<|<.<) (*|decimalExpression)]


A Float type represents floating-point values with a given range of valid values (via the Minimum and Maximum attributes).
The MinInclusive and MaxInclusive attributes determine whether the boundaries are included in the range or not. @@ -171,11 +171,23 @@ class XsmpcatKeywordHovers implements IKeywordHovers {
The metaclasses derived from SimpleValue, however, are pre-defined and cannot be extended.

''' case ga.namespaceMemberAccess.nativeKeyword_3_14_2: ''' +

+ + /**
+ * @namespace native_namespace
+ * @type native_typename
+ * @location native_location
+ */

+ «visibility()» «kw("native")» name +

+
A Native Type specifies a type with any number of platform mappings. It is used to anchor existing or user-defined types into different target platforms.

This mechanism is used within the specification to define the SMDL primitive types with respect to the Metamodel, but it can also be used to define native types within an arbitrary SMDL catalogue for use by models.
In the latter case, native types are typically used to bind a model to some external library or existing Application Programming Interface (API).

''' case ga.namespaceMemberAccess.attributeKeyword_3_15_2: ''' +

«visibility()» «kw("attribute")» type name = defaultValueExpression

+
An Attribute Type defines a new type available for adding attributes to elements.


The AllowMultiple attribute specifies if a corresponding Attribute may be attached more than once to a language element, while the Usage element defines to which language elements attributes of this type can be attached.
An attribute type always references a value type, and specifies a Default value.

@@ -200,7 +212,7 @@ class XsmpcatKeywordHovers implements IKeywordHovers {
These flags default to false, but can be changed from their default value to support dataflow-based design.

''' case ga.constantDeclarationAccess.constantKeyword_1: ''' -

«visibility()» «kw("constant")» type name = valueExpression

+

«visibility()» «kw("constant")» type name = valueExpression


A Constant is a feature that is typed by a simple type and that must have a Value. ''' @@ -292,6 +304,18 @@ class XsmpcatKeywordHovers implements IKeywordHovers { case ga.visibilityModifiersAccess.publicKeyword_2: ''' The element is globally visible. ''' + case ga.classModifiersAccess.privateKeyword_0: ''' + The type is visible only within its containing namespace. + ''' + case ga.classModifiersAccess.protectedKeyword_1: ''' + The type is visible within its containing containing Catalogue. + ''' + case ga.classModifiersAccess.publicKeyword_2: ''' + The type is globally visible. + ''' + case ga.classModifiersAccess.abstractKeyword_3: ''' + The type is abstract. Abstract types cannot be instantiated, but they can be subtyped. + ''' case ga.parameterDirectionKindAccess.inInKeyword_0_0: ''' The parameter is read-only to the operation, i.e. its value must be specified on call, and cannot be changed inside the operation. ''' diff --git a/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/hover/XsmpprojectKeywordHovers.xtend b/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/hover/XsmpprojectKeywordHovers.xtend index a3b51d7f..b5ed062a 100644 --- a/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/hover/XsmpprojectKeywordHovers.xtend +++ b/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/hover/XsmpprojectKeywordHovers.xtend @@ -46,10 +46,16 @@ class XsmpprojectKeywordHovers implements IKeywordHovers { case ga.sourcePathAccess.sourceKeyword_0: '''

«kw("source")» name


- The Source defines the location of modeling files. + The Source directive defines the location of modeling files.
It must be relative to project root.
It can be a folder or a file name. ''' + case ga.sourcePathAccess.sourceKeyword_0: ''' +

«kw("include")» name

+
+ The Include directive defines the location of nested project(s). +
It must be relative to project root. + ''' case ga.projectReferenceAccess.dependencyKeyword_0: '''

«kw("dependency")» name


diff --git a/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/labeling/XsmpLabelProvider.java b/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/labeling/XsmpLabelProvider.java index 06e1caf5..e30347e5 100644 --- a/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/labeling/XsmpLabelProvider.java +++ b/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/labeling/XsmpLabelProvider.java @@ -10,7 +10,6 @@ ******************************************************************************/ package org.eclipse.xsmp.ide.labeling; -import java.net.URI; import java.util.Collections; import java.util.stream.Collectors; @@ -298,11 +297,11 @@ protected String text(Reference elem) protected String text(SourcePath ele) { - if (".".equals(ele.getName())) + if (ele.getName() == null || ele.getName().isEmpty()) { - return ele.getName(); + return "."; } - return URI.create(ele.getName()).normalize().getPath(); + return ele.getName(); } protected String text(ProjectReference ele) diff --git a/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/symbol/XsmpDocumentSymbolDetailsProvider.java b/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/symbol/XsmpDocumentSymbolDetailsProvider.java index 5a5dc88d..62d35aff 100644 --- a/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/symbol/XsmpDocumentSymbolDetailsProvider.java +++ b/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/symbol/XsmpDocumentSymbolDetailsProvider.java @@ -17,6 +17,7 @@ import org.eclipse.emf.ecore.EObject; import org.eclipse.xsmp.model.xsmp.VisibilityElement; +import org.eclipse.xsmp.model.xsmp.XsmpPackage; import org.eclipse.xtext.ide.server.symbol.DocumentSymbolMapper.DocumentSymbolDetailsProvider; public class XsmpDocumentSymbolDetailsProvider extends DocumentSymbolDetailsProvider @@ -34,7 +35,16 @@ public String getDetails(EObject object) switch (object.eClass().getClassifierID()) { - case SOURCE_PATH -> builder.append("Source"); + case SOURCE_PATH -> { + if (object.eContainingFeature() == XsmpPackage.Literals.PROJECT__SOURCES) + { + builder.append("Source"); + } + else + { + builder.append("Include"); + } + } case TOOL_REFERENCE -> builder.append("Tool"); case PROFILE_REFERENCE -> builder.append("Profile"); case PROJECT_REFERENCE -> builder.append("Dependency"); diff --git a/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/workspace/XsmpProjectConfig.java b/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/workspace/XsmpProjectConfig.java index 3d8448ae..f520a216 100644 --- a/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/workspace/XsmpProjectConfig.java +++ b/org.eclipse.xsmp.ide/src/org/eclipse/xsmp/ide/workspace/XsmpProjectConfig.java @@ -10,16 +10,22 @@ ******************************************************************************/ package org.eclipse.xsmp.ide.workspace; -import java.net.URI; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.findFirst; + +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; +import org.eclipse.emf.common.util.URI; import org.eclipse.xsmp.model.xsmp.Project; import org.eclipse.xsmp.model.xsmp.ProjectReference; import org.eclipse.xsmp.model.xsmp.ToolReference; import org.eclipse.xsmp.workspace.IXsmpProjectConfig; import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.workspace.FileProjectConfig; +import org.eclipse.xtext.workspace.FileSourceFolder; +import org.eclipse.xtext.workspace.ISourceFolder; import org.eclipse.xtext.workspace.IWorkspaceConfig; public class XsmpProjectConfig extends FileProjectConfig implements IXsmpProjectConfig @@ -31,6 +37,8 @@ public class XsmpProjectConfig extends FileProjectConfig implements IXsmpProject private final List dependencies; + private final Set includeFolders = new HashSet<>(); + public XsmpProjectConfig(Project project, IWorkspaceConfig workspaceConfig) { super(EcoreUtil2.getNormalizedResourceURI(project).trimSegments(1), project.getName(), @@ -41,8 +49,15 @@ public XsmpProjectConfig(Project project, IWorkspaceConfig workspaceConfig) dependencies = project.getReferencedProjects().stream().map(ProjectReference::getName) .filter(Objects::nonNull).toList(); - project.getSources().forEach(folder -> addSourceFolder(".".equals(folder.getName()) ? "" - : URI.create(folder.getName()).normalize().getPath())); + project.getSources().forEach(folder -> addSourceFolder(folder.getName())); + project.getIncludes().forEach(folder -> addIncludeFolder(folder.getName())); + } + + public FileSourceFolder addIncludeFolder(String relativePath) + { + final var includeFolder = new FileSourceFolder(this, relativePath); + includeFolders.add(includeFolder); + return includeFolder; } @Override @@ -75,4 +90,16 @@ public int hashCode() return super.hashCode(); } + @Override + public Set< ? extends ISourceFolder> getIncludeFolders() + { + return includeFolders; + } + + @Override + public ISourceFolder findIncludeFolderContaining(URI member) + { + return findFirst(includeFolders, f -> f.contains(member)); + } + } diff --git a/org.eclipse.xsmp.server/src/org/eclipse/xsmp/server/XsmpProjectConfigFactory.java b/org.eclipse.xsmp.server/src/org/eclipse/xsmp/server/XsmpProjectConfigFactory.java index 5425cfae..2d496152 100644 --- a/org.eclipse.xsmp.server/src/org/eclipse/xsmp/server/XsmpProjectConfigFactory.java +++ b/org.eclipse.xsmp.server/src/org/eclipse/xsmp/server/XsmpProjectConfigFactory.java @@ -18,6 +18,7 @@ import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.util.function.Predicate; import org.eclipse.emf.common.util.URI; import org.eclipse.xsmp.XsmpConstants; @@ -30,6 +31,44 @@ public class XsmpProjectConfigFactory extends MultiProjectWorkspaceConfigFactory { + private final class ProjectVisitor extends SimpleFileVisitor + { + private final WorkspaceConfig workspaceConfig; + + private final Predicate filter; + + private ProjectVisitor(WorkspaceConfig workspaceConfig, Predicate filter) + { + this.workspaceConfig = workspaceConfig; + this.filter = filter; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException + { + if (attrs.isSymbolicLink()) + { + return FileVisitResult.SKIP_SUBTREE; + } + final var xsmpProjectFile = dir.resolve(XsmpConstants.XSMP_PROJECT_FILENAME); + if (filter.test(dir) && Files.exists(xsmpProjectFile)) + { + final var config = projectConfigProvider.createProjectConfig( + URI.createFileURI(xsmpProjectFile.toString()), workspaceConfig); + workspaceConfig.addProject(config); + if (!config.getIncludeFolders().isEmpty()) + { + Files.walkFileTree(Paths.get(config.getPath().path()), + new ProjectVisitor(workspaceConfig, p -> !p.equals(dir) && config + .findIncludeFolderContaining(URI.createFileURI(p.toString())) != null)); + } + + return FileVisitResult.SKIP_SUBTREE; + } + return super.preVisitDirectory(dir, attrs); + } + } + @Inject private XsmpProjectConfigProvider projectConfigProvider; @@ -47,25 +86,8 @@ public void findProjects(WorkspaceConfig workspaceConfig, URI uri) } try { - Files.walkFileTree(Paths.get(baseFile.toURI()), new SimpleFileVisitor() { - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) - throws IOException - { - if (attrs.isSymbolicLink()) - { - return FileVisitResult.SKIP_SUBTREE; - } - final var xsmpProjectFile = dir.resolve(XsmpConstants.XSMP_PROJECT_FILENAME); - if (Files.exists(xsmpProjectFile)) - { - workspaceConfig.addProject(projectConfigProvider.createProjectConfig( - URI.createFileURI(xsmpProjectFile.toString()), workspaceConfig)); - return FileVisitResult.SKIP_SUBTREE; - } - return super.preVisitDirectory(dir, attrs); - } - }); + Files.walkFileTree(Paths.get(baseFile.toURI()), + new ProjectVisitor(workspaceConfig, p -> true)); } catch (final IOException e) { diff --git a/org.eclipse.xsmp.ui/src/org/eclipse/xsmp/ui/containers/XsmpProjectsStateHelper.java b/org.eclipse.xsmp.ui/src/org/eclipse/xsmp/ui/containers/XsmpProjectsStateHelper.java index 46dec7f1..1ff34acb 100644 --- a/org.eclipse.xsmp.ui/src/org/eclipse/xsmp/ui/containers/XsmpProjectsStateHelper.java +++ b/org.eclipse.xsmp.ui/src/org/eclipse/xsmp/ui/containers/XsmpProjectsStateHelper.java @@ -68,13 +68,13 @@ public Collection initContainedURIs(String containerHandle) final Set result = new HashSet<>(); for (final var source : settings.getSourceFolders()) { - if (".".equals(source.getName()) || source.getName().isEmpty()) + final var path = source.getName(); + if (path.isEmpty()) { result.addAll(getMapper().getAllEntries(project).keySet()); } else { - final var path = source.getName(); final var f = project.getFolder(path); if (f.exists()) { diff --git a/org.eclipse.xsmp.ui/src/org/eclipse/xsmp/ui/labeling/XsmpprojectLabelProvider.java b/org.eclipse.xsmp.ui/src/org/eclipse/xsmp/ui/labeling/XsmpprojectLabelProvider.java index 562a70f7..bb936b75 100644 --- a/org.eclipse.xsmp.ui/src/org/eclipse/xsmp/ui/labeling/XsmpprojectLabelProvider.java +++ b/org.eclipse.xsmp.ui/src/org/eclipse/xsmp/ui/labeling/XsmpprojectLabelProvider.java @@ -10,8 +10,6 @@ ******************************************************************************/ package org.eclipse.xsmp.ui.labeling; -import java.net.URI; - import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider; import org.eclipse.xsmp.model.xsmp.Document; import org.eclipse.xsmp.model.xsmp.ProfileReference; @@ -47,11 +45,11 @@ String image(Document ele) String text(SourcePath ele) { - if (".".equals(ele.getName())) + if (ele.getName() == null || ele.getName().isEmpty()) { - return ele.getName(); + return "."; } - return URI.create(ele.getName()).normalize().getPath(); + return ele.getName(); } String image(SourcePath ele) diff --git a/org.eclipse.xsmp.ui/src/org/eclipse/xsmp/ui/validation/XsmpprojectUIValidator.java b/org.eclipse.xsmp.ui/src/org/eclipse/xsmp/ui/validation/XsmpprojectUIValidator.java index a966e539..5f6de887 100644 --- a/org.eclipse.xsmp.ui/src/org/eclipse/xsmp/ui/validation/XsmpprojectUIValidator.java +++ b/org.eclipse.xsmp.ui/src/org/eclipse/xsmp/ui/validation/XsmpprojectUIValidator.java @@ -42,27 +42,17 @@ protected void checkProject(Project p) try { final var name = source.getName(); - if (".".equals(name)) + if (!".".equals(name) && !project.getFolder(name).exists() + && !project.getFile(name).exists()) { - continue; - } - if (name.startsWith("/") || name.contains("../")) - { - acceptError("Source '" + source.getName() + "' is invalid", source, + acceptError("Source '" + source.getName() + "' does not exist.", source, XsmpPackage.Literals.SOURCE_PATH__NAME, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); } - if (project.getFolder(name).exists() || project.getFile(name).exists()) - { - continue; - } - acceptError("Source '" + source.getName() + "' does not exist.", source, - XsmpPackage.Literals.SOURCE_PATH__NAME, - ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); } catch (final IllegalArgumentException e) { - acceptError("Source '" + source.getName() + "' is invalid", source, + acceptError("Source '" + source.getName() + "' is invalid.", source, XsmpPackage.Literals.SOURCE_PATH__NAME, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); } diff --git a/org.eclipse.xsmp.ui/src/org/eclipse/xsmp/ui/workspace/XsmpEclipseProjectConfig.java b/org.eclipse.xsmp.ui/src/org/eclipse/xsmp/ui/workspace/XsmpEclipseProjectConfig.java index 285ee076..1f3ab9bb 100644 --- a/org.eclipse.xsmp.ui/src/org/eclipse/xsmp/ui/workspace/XsmpEclipseProjectConfig.java +++ b/org.eclipse.xsmp.ui/src/org/eclipse/xsmp/ui/workspace/XsmpEclipseProjectConfig.java @@ -10,7 +10,6 @@ ******************************************************************************/ package org.eclipse.xsmp.ui.workspace; -import java.net.URI; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -18,6 +17,7 @@ import java.util.Set; import org.eclipse.core.resources.IProject; +import org.eclipse.emf.common.util.URI; import org.eclipse.xsmp.model.xsmp.Project; import org.eclipse.xsmp.model.xsmp.ProjectReference; import org.eclipse.xsmp.model.xsmp.ToolReference; @@ -67,7 +67,7 @@ public XsmpEclipseProjectConfig(IProject eclipseProject, private ISourceFolder addSourceFolder(String relativePath) { final var sourceFolder = new EclipseSourceFolder(getProject(), - ".".equals(relativePath) ? "" : URI.create(relativePath).normalize().getPath()); + ".".equals(relativePath) ? "" : relativePath); sourceFolders.add(sourceFolder); return sourceFolder; } @@ -107,4 +107,16 @@ public String toString() { return new ToStringBuilder(this).addAllFields().toString(); } + + @Override + public Set< ? extends ISourceFolder> getIncludeFolders() + { + return Collections.emptySet(); + } + + @Override + public ISourceFolder findIncludeFolderContaining(URI member) + { + return null; + } } diff --git a/org.eclipse.xsmp/model/xsmp.ecore b/org.eclipse.xsmp/model/xsmp.ecore index 87d8858b..ac07271f 100644 --- a/org.eclipse.xsmp/model/xsmp.ecore +++ b/org.eclipse.xsmp/model/xsmp.ecore @@ -84,7 +84,7 @@ - @@ -356,6 +356,8 @@ eType="#//SourcePath" containment="true" resolveProxies="false"/> + diff --git a/org.eclipse.xsmp/model/xsmp.genmodel b/org.eclipse.xsmp/model/xsmp.genmodel index b690fa05..5c49570f 100644 --- a/org.eclipse.xsmp/model/xsmp.genmodel +++ b/org.eclipse.xsmp/model/xsmp.genmodel @@ -260,6 +260,7 @@ + diff --git a/org.eclipse.xsmp/src/org/eclipse/xsmp/Xsmpcat.xtext b/org.eclipse.xsmp/src/org/eclipse/xsmp/Xsmpcat.xtext index 5b022c16..0c9f59c8 100644 --- a/org.eclipse.xsmp/src/org/eclipse/xsmp/Xsmpcat.xtext +++ b/org.eclipse.xsmp/src/org/eclipse/xsmp/Xsmpcat.xtext @@ -221,7 +221,7 @@ VisibilityModifiers: 'private' | 'protected' | 'public'; ClassModifiers: - VisibilityModifiers | 'abstract'; + 'private' | 'protected' | 'public' | 'abstract'; PropertyModifiers: VisibilityModifiers | 'readWrite' | 'readOnly' | 'writeOnly'; diff --git a/org.eclipse.xsmp/src/org/eclipse/xsmp/Xsmpproject.xtext b/org.eclipse.xsmp/src/org/eclipse/xsmp/Xsmpproject.xtext index ec89258d..319c013c 100644 --- a/org.eclipse.xsmp/src/org/eclipse/xsmp/Xsmpproject.xtext +++ b/org.eclipse.xsmp/src/org/eclipse/xsmp/Xsmpproject.xtext @@ -17,9 +17,9 @@ import "http://org.eclipse.xsmp/xsmp" Project: metadatum=Metadatum 'project' name=STRING - ((tools+=ToolReference | sources+=SourcePath | referencedProjects+=ProjectReference)* + ((tools+=ToolReference | sources+=SourcePath | referencedProjects+=ProjectReference | includes+=IncludePath)* profile=ProfileReference)? - (tools+=ToolReference | sources+=SourcePath | referencedProjects+=ProjectReference)*; + (tools+=ToolReference | sources+=SourcePath | referencedProjects+=ProjectReference | includes+=IncludePath)*; Profile: metadatum=Metadatum @@ -38,6 +38,9 @@ ToolReference: SourcePath: 'source' name=STRING; +IncludePath returns SourcePath: + 'include' name=STRING; + ProjectReference: 'dependency' project=[Project|STRING]; diff --git a/org.eclipse.xsmp/src/org/eclipse/xsmp/formatting2/XsmpprojectFormatter.java b/org.eclipse.xsmp/src/org/eclipse/xsmp/formatting2/XsmpprojectFormatter.java index 651b478a..e12fc9ec 100644 --- a/org.eclipse.xsmp/src/org/eclipse/xsmp/formatting2/XsmpprojectFormatter.java +++ b/org.eclipse.xsmp/src/org/eclipse/xsmp/formatting2/XsmpprojectFormatter.java @@ -54,6 +54,10 @@ protected void format(Project project, IFormattableDocument doc) { doc.format(dependency); } + for (final var include : project.getIncludes()) + { + doc.format(include); + } } public ITextReplacer createDocumentationReplacer(ISemanticRegion description) diff --git a/org.eclipse.xsmp/src/org/eclipse/xsmp/model/xsmp/impl/SourcePathImplCustom.java b/org.eclipse.xsmp/src/org/eclipse/xsmp/model/xsmp/impl/SourcePathImplCustom.java new file mode 100644 index 00000000..2f2da60a --- /dev/null +++ b/org.eclipse.xsmp/src/org/eclipse/xsmp/model/xsmp/impl/SourcePathImplCustom.java @@ -0,0 +1,31 @@ +/** + * ****************************************************************************** + * Copyright (C) 2024 THALES ALENIA SPACE FRANCE. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * ***************************************************************************** + */ +package org.eclipse.xsmp.model.xsmp.impl; + +import java.net.URI; + +public class SourcePathImplCustom extends SourcePathImpl +{ + @Override + public void setName(String newName) + { + if (newName == null) + { + super.setName(newName); + } + final var normalized = URI.create(newName).normalize().getPath(); + + super.setName(normalized.isEmpty() ? "." : normalized); + } + +} // SourcePathImplCustom diff --git a/org.eclipse.xsmp/src/org/eclipse/xsmp/validation/XsmpcatValidator.java b/org.eclipse.xsmp/src/org/eclipse/xsmp/validation/XsmpcatValidator.java index c6740441..1bf5e83b 100644 --- a/org.eclipse.xsmp/src/org/eclipse/xsmp/validation/XsmpcatValidator.java +++ b/org.eclipse.xsmp/src/org/eclipse/xsmp/validation/XsmpcatValidator.java @@ -49,6 +49,7 @@ import org.eclipse.xsmp.model.xsmp.NamedElement; import org.eclipse.xsmp.model.xsmp.NamedElementReference; import org.eclipse.xsmp.model.xsmp.NamedElementWithMultiplicity; +import org.eclipse.xsmp.model.xsmp.NativeType; import org.eclipse.xsmp.model.xsmp.Operation; import org.eclipse.xsmp.model.xsmp.Parameter; import org.eclipse.xsmp.model.xsmp.Property; @@ -658,8 +659,7 @@ protected void checkFloat(org.eclipse.xsmp.model.xsmp.Float elem) } break; default: - error("Expecting a Floating Point Type, got " + kind.name() + ".", - XsmpPackage.Literals.FLOAT__PRIMITIVE_TYPE); + error("Expecting a Floating Point Type.", XsmpPackage.Literals.FLOAT__PRIMITIVE_TYPE); break; } @@ -688,8 +688,7 @@ protected void checkInteger(org.eclipse.xsmp.model.xsmp.Integer elem) } break; default: - error("Expecting an Integral Type, got " + kind.name() + ".", - XsmpPackage.Literals.INTEGER__PRIMITIVE_TYPE); + error("Expecting an Integral Type.", XsmpPackage.Literals.INTEGER__PRIMITIVE_TYPE); } } @@ -1010,6 +1009,15 @@ protected void checkEventType(EventType elem) checkTypeReference(elem.getEventArgs(), elem, XsmpPackage.Literals.EVENT_TYPE__EVENT_ARGS); } + @Check + protected void checkNativeType(NativeType elem) + { + if (elem.getType() == null || elem.getType().isEmpty()) + { + error("Native type must be defined.", XsmpPackage.Literals.NATIVE_TYPE__TYPE); + } + } + @Check protected void checkEventSink(EventSink elem) { diff --git a/org.eclipse.xsmp/src/org/eclipse/xsmp/validation/XsmpprojectValidator.java b/org.eclipse.xsmp/src/org/eclipse/xsmp/validation/XsmpprojectValidator.java index a00704a9..718cc7f0 100644 --- a/org.eclipse.xsmp/src/org/eclipse/xsmp/validation/XsmpprojectValidator.java +++ b/org.eclipse.xsmp/src/org/eclipse/xsmp/validation/XsmpprojectValidator.java @@ -40,23 +40,49 @@ protected void checkProject(Project p) if (!visitedSources.add(name)) { - acceptError("Duplicated source folder '" + name + "'.", source, null, INSIGNIFICANT_INDEX, + acceptError("Duplicated Source path '" + name + "'.", source, null, INSIGNIFICANT_INDEX, null); } if (name.startsWith("/")) { - acceptError("Source folder '" + source.getName() + "' must be relative to project path.", + acceptError("Source path '" + source.getName() + "' is not relative to project path.", source, XsmpPackage.Literals.SOURCE_PATH__NAME, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); } - if (name.contains("../")) + if (name.startsWith("../") || "..".equals(name)) { - acceptError("Source folder '" + source.getName() + "' must not contain '../'.", source, - XsmpPackage.Literals.SOURCE_PATH__NAME, + acceptError( + "Source path '" + source.getName() + + "' is not contained within the project directory.", + source, XsmpPackage.Literals.SOURCE_PATH__NAME, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); } } + final var visitedIncludes = new HashSet(); + for (final var include : p.getIncludes()) + { + final var name = include.getName(); + if (!visitedIncludes.add(name)) + { + acceptError("Duplicated Include path '" + name + "'.", include, null, INSIGNIFICANT_INDEX, + null); + } + if (name.startsWith("/")) + { + acceptError("Include path '" + include.getName() + "' is not relative to project path.", + include, XsmpPackage.Literals.SOURCE_PATH__NAME, + ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); + } + if (name.startsWith("../") || "..".equals(name)) + { + acceptError( + "Include path '" + include.getName() + + "' is not contained within the project directory.", + include, XsmpPackage.Literals.SOURCE_PATH__NAME, + ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); + } + } final var visitedDependencies = new HashSet(); // check no duplicate dependency and different that current project for (final var dependency : p.getReferencedProjects()) diff --git a/org.eclipse.xsmp/src/org/eclipse/xsmp/workspace/IXsmpProjectConfig.java b/org.eclipse.xsmp/src/org/eclipse/xsmp/workspace/IXsmpProjectConfig.java index bc5e9147..6809f501 100644 --- a/org.eclipse.xsmp/src/org/eclipse/xsmp/workspace/IXsmpProjectConfig.java +++ b/org.eclipse.xsmp/src/org/eclipse/xsmp/workspace/IXsmpProjectConfig.java @@ -11,8 +11,11 @@ package org.eclipse.xsmp.workspace; import java.util.Collection; +import java.util.Set; +import org.eclipse.emf.common.util.URI; import org.eclipse.xtext.workspace.IProjectConfig; +import org.eclipse.xtext.workspace.ISourceFolder; public interface IXsmpProjectConfig extends IProjectConfig { @@ -22,4 +25,13 @@ public interface IXsmpProjectConfig extends IProjectConfig Collection getTools(); Collection getDependencies(); + + /** @return a set of all include folders */ + Set< ? extends ISourceFolder> getIncludeFolders(); + + /** + * Finds the source folder that physically contains this member or null if none was found. + */ + ISourceFolder findIncludeFolderContaining(URI member); + } diff --git a/vscode-extension/syntaxes/xsmpcat.tmLanguage.json b/vscode-extension/syntaxes/xsmpcat.tmLanguage.json index 1af5a6fd..310535df 100644 --- a/vscode-extension/syntaxes/xsmpcat.tmLanguage.json +++ b/vscode-extension/syntaxes/xsmpcat.tmLanguage.json @@ -9,10 +9,6 @@ "match": "\\b(true|false|nullptr)\\b", "name": "constant.language.xsmp.catalogue" }, - { - "match": "\\b([a-zA-Z]\\w*)\\b", - "name": "entity.name.type.xsmp.catalogue" - }, { "match": "\\b(void)\\b", "name": "storage.type.primitive.xsmp.catalogue" @@ -54,6 +50,10 @@ }, { "include": "#characters" + }, + { + "name": "storage.type.annotation.xsmp", + "match": "@\\s*(?:[a-zA-Z_]\\w*\\s*\\.\\s*)*[a-zA-Z_]\\w*" } ], "repository": { diff --git a/vscode-extension/syntaxes/xsmpproject.tmLanguage.json b/vscode-extension/syntaxes/xsmpproject.tmLanguage.json index 76a6c22a..03794ff6 100644 --- a/vscode-extension/syntaxes/xsmpproject.tmLanguage.json +++ b/vscode-extension/syntaxes/xsmpproject.tmLanguage.json @@ -2,7 +2,7 @@ "scopeName": "source.xsmpproject", "patterns": [ { - "match": "\\b(project|profile|tool|source|dependency)\\b", + "match": "\\b(project|profile|tool|source|dependency|include)\\b", "name": "keyword.control.xsmp.project" }, {