diff --git a/core/ts.core/src/ts/repository/ITypeScriptRepositoryManager.java b/core/ts.core/src/ts/repository/ITypeScriptRepositoryManager.java index 4172731b..0cdc107b 100644 --- a/core/ts.core/src/ts/repository/ITypeScriptRepositoryManager.java +++ b/core/ts.core/src/ts/repository/ITypeScriptRepositoryManager.java @@ -2,18 +2,70 @@ import java.io.File; +/** + * Manager for keeping track of the available TypeScript repositories. Each + * repository includes a TypeScript installation and additional software, such + * as TSLint. + * + * Each repository is automatically assigned a name according to the included + * TypeScript version. No two repositories may share the same name. + * + * One repository may be marked as the default (see + * {@link #getDefaultRepository()}). + * + */ public interface ITypeScriptRepositoryManager { + /** + * Creates and adds a new repository. The new repository is also set as the + * default. + * + * @param baseDir + * base directory of the new repository. + * @return the created repository. + */ ITypeScriptRepository createDefaultRepository(File baseDir) throws TypeScriptRepositoryException; + /** + * Creates and adds a new repository. + * + * @param baseDir + * base directory of the new repository. + * @return the created repository. + */ ITypeScriptRepository createRepository(File baseDir) throws TypeScriptRepositoryException; + /** + * Removes a repository. If not present, nothing happens. + * + * @param name + * name of the repository to remove. + * @return the removed repository. + */ ITypeScriptRepository removeRepository(String name); + /** + * Gets the current default repository. + * + * @return a repository or {@code null} if there is no default. + */ ITypeScriptRepository getDefaultRepository(); + /** + * Gets a managed repository by name. + * + * @param name + * name of the repository to retrieve. + * @return a repository or {@code null} if there is no repository with the + * requested name. + */ ITypeScriptRepository getRepository(String name); + /** + * Gets all registered repositories. + * + * @return array of repositories. + */ ITypeScriptRepository[] getRepositories(); } diff --git a/core/ts.core/src/ts/repository/TypeScriptRepositoryManager.java b/core/ts.core/src/ts/repository/TypeScriptRepositoryManager.java index ed700664..3d87cedf 100644 --- a/core/ts.core/src/ts/repository/TypeScriptRepositoryManager.java +++ b/core/ts.core/src/ts/repository/TypeScriptRepositoryManager.java @@ -1,3 +1,14 @@ +/** + * Copyright (c) 2015-2016 Angelo ZERR. + * 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: + * Angelo Zerr - initial API and implementation + * Lorenzo Dalla Vecchia - protected API for setting default + */ package ts.repository; import java.io.File; @@ -26,6 +37,10 @@ public ITypeScriptRepository createDefaultRepository(File baseDir) throws TypeSc return this.defaultRepository = createRepository(baseDir); } + protected final void setDefaultRepository(ITypeScriptRepository repository) { + this.defaultRepository = repository; + } + @Override public ITypeScriptRepository createRepository(File baseDir) throws TypeScriptRepositoryException { synchronized (repositories) { diff --git a/core/ts.repository/META-INF/MANIFEST.MF b/core/ts.repository/META-INF/MANIFEST.MF index 760ffb36..560f06a4 100644 --- a/core/ts.repository/META-INF/MANIFEST.MF +++ b/core/ts.repository/META-INF/MANIFEST.MF @@ -3,9 +3,10 @@ Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-Vendor: %providerName Bundle-Localization: plugin -Bundle-SymbolicName: ts.repository +Bundle-SymbolicName: ts.repository;singleton:=true Bundle-Version: 1.2.0.qualifier Eclipse-BundleShape: dir Import-Package: org.osgi.framework Bundle-Activator: ts.Activator Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Require-Bundle: ts.eclipse.ide.core diff --git a/core/ts.repository/build.properties b/core/ts.repository/build.properties index 3c0c6a65..eba7b9f3 100644 --- a/core/ts.repository/build.properties +++ b/core/ts.repository/build.properties @@ -3,4 +3,5 @@ output.. = bin/ bin.includes = META-INF/,\ .,\ plugin.properties,\ - node_modules/ + node_modules/,\ + plugin.xml diff --git a/core/ts.repository/plugin.xml b/core/ts.repository/plugin.xml new file mode 100644 index 00000000..181aed44 --- /dev/null +++ b/core/ts.repository/plugin.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/eclipse/ts.eclipse.ide.core/META-INF/MANIFEST.MF b/eclipse/ts.eclipse.ide.core/META-INF/MANIFEST.MF index b1a20270..3402f779 100644 --- a/eclipse/ts.eclipse.ide.core/META-INF/MANIFEST.MF +++ b/eclipse/ts.eclipse.ide.core/META-INF/MANIFEST.MF @@ -8,7 +8,6 @@ Bundle-Version: 1.2.0.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Require-Bundle: org.eclipse.core.runtime, org.eclipse.core.resources, - ts.repository, ts.core, ts.eclipse, org.eclipse.jface.text, diff --git a/eclipse/ts.eclipse.ide.core/plugin.properties b/eclipse/ts.eclipse.ide.core/plugin.properties index 03b80e9c..2cf7b996 100644 --- a/eclipse/ts.eclipse.ide.core/plugin.properties +++ b/eclipse/ts.eclipse.ide.core/plugin.properties @@ -25,6 +25,7 @@ TypeScriptBuilder.name=TypeScript Builder # Extension Points nodeJSInstallsContribution.name=Node.js Installations typeScriptConsoleConnectors.name=TypeScript Console Connector Extension Points +typeScriptRepositoriesContribution.name=TypeScript Repositories # Launch TypeScriptCompilerLaunchConfigurationDelegate.label=TypeScript Compile \ No newline at end of file diff --git a/eclipse/ts.eclipse.ide.core/plugin.xml b/eclipse/ts.eclipse.ide.core/plugin.xml index d319cecd..d124c902 100644 --- a/eclipse/ts.eclipse.ide.core/plugin.xml +++ b/eclipse/ts.eclipse.ide.core/plugin.xml @@ -20,6 +20,7 @@ + + + + + + + + + Extension point to provided embedded TypeScript Repositories. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This extension point allows developers to supply a repository containing a TypeScript installation as well as the TSLint tool. +<p> +A repository is represented by a root directory containing a <i>node_modules</i> directory, itself containing the <i>typescript</i> and <i>tslit</i> packages. +<p> +The version of TypeScript embedded in the repository directory is detected automatically. On first startup, the repository containing the most recent version is selected as active repository. The user is always free to change the active repository. + + + + + + + The base directory of the repository inside the contributing bundle. + + + + + + + + + + + + 2.0 + + + + + + + + + + + This plugin itself does not have any predefined repositories + + + + + diff --git a/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/core/TypeScriptCorePlugin.java b/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/core/TypeScriptCorePlugin.java index 04bd508e..24eb65ae 100644 --- a/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/core/TypeScriptCorePlugin.java +++ b/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/core/TypeScriptCorePlugin.java @@ -10,12 +10,7 @@ */ package ts.eclipse.ide.core; -import java.io.File; -import java.io.IOException; - -import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Plugin; import org.eclipse.core.runtime.Status; import org.osgi.framework.BundleContext; @@ -57,16 +52,6 @@ public void start(BundleContext context) throws Exception { resourceManager.setTypeScriptResourcesManagerDelegate(IDEResourcesManager.getInstance()); } - /** - * Returns the TypeScript repository base directory. - * - * @return the TypeScript repository base directory. - * @throws IOException - */ - public static File getTypeScriptRepositoryBaseDir() throws IOException { - return FileLocator.getBundleFile(Platform.getBundle("ts.repository")); - } - @Override public void stop(BundleContext context) throws Exception { ResourcesWatcher.getInstance().dispose(); diff --git a/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/core/repository/IIDETypeScriptRepositoryManager.java b/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/core/repository/IIDETypeScriptRepositoryManager.java index 7eba622f..0e973310 100644 --- a/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/core/repository/IIDETypeScriptRepositoryManager.java +++ b/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/core/repository/IIDETypeScriptRepositoryManager.java @@ -6,6 +6,13 @@ import ts.repository.ITypeScriptRepositoryManager; +/** + * TypeScript repository manager for use in the Eclipse IDE. + * + * In addition to the repositories registered manually, this manager will also + * pick up any repository contributed to the {@code typeScriptRepositories} + * extension point. + */ public interface IIDETypeScriptRepositoryManager extends ITypeScriptRepositoryManager { String generateFileName(IResource resource, IProject project); diff --git a/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/preferences/TypeScriptCorePreferenceInitializer.java b/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/preferences/TypeScriptCorePreferenceInitializer.java index c17c143d..b51a57ac 100644 --- a/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/preferences/TypeScriptCorePreferenceInitializer.java +++ b/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/preferences/TypeScriptCorePreferenceInitializer.java @@ -10,10 +10,6 @@ */ package ts.eclipse.ide.internal.core.preferences; -import java.io.File; - -import org.eclipse.core.runtime.FileLocator; -import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.osgi.service.prefs.BackingStoreException; @@ -45,26 +41,10 @@ public void initializeDefaultPreferences() { initializeNodejsPreferences(node); try { - File tsRepositoryBaseDir = FileLocator.getBundleFile(Platform.getBundle("ts.repository")); ITypeScriptRepository defaultRepository = TypeScriptCorePlugin.getTypeScriptRepositoryManager() - .createDefaultRepository(tsRepositoryBaseDir); - - // Loop for archives of TypeScript (1.8.10, etc) - File archivesDir = new File(tsRepositoryBaseDir, "archives"); - if (archivesDir.exists()) { - File[] oldRepostoryBaseDirs = archivesDir.listFiles(); - File oldRepostoryBaseDir = null; - for (int i = 0; i < oldRepostoryBaseDirs.length; i++) { - oldRepostoryBaseDir = oldRepostoryBaseDirs[i]; - if (oldRepostoryBaseDir.isDirectory()) { - try { - TypeScriptCorePlugin.getTypeScriptRepositoryManager().createRepository(oldRepostoryBaseDir); - } catch (Exception e) { - Trace.trace(Trace.SEVERE, "Error while getting an archived TypeScript repository", e); - } - } - } - + .getDefaultRepository(); + if (defaultRepository == null) { + Trace.trace(Trace.WARNING, "No default TypeScript repository is available"); } // Initialize TypeScript runtime preferences @@ -126,8 +106,12 @@ private static boolean useBundledNodeJsEmbedded(IEclipsePreferences node) { private void initializeTypeScriptRuntimePreferences(IEclipsePreferences node, ITypeScriptRepository defaultRepository) { - node.put(TypeScriptCorePreferenceConstants.EMBEDDED_TYPESCRIPT_ID, defaultRepository.getName()); - node.putBoolean(TypeScriptCorePreferenceConstants.USE_EMBEDDED_TYPESCRIPT, true); + if (defaultRepository != null) { + node.put(TypeScriptCorePreferenceConstants.EMBEDDED_TYPESCRIPT_ID, defaultRepository.getName()); + node.putBoolean(TypeScriptCorePreferenceConstants.USE_EMBEDDED_TYPESCRIPT, true); + } else { + node.putBoolean(TypeScriptCorePreferenceConstants.USE_EMBEDDED_TYPESCRIPT, false); + } node.put(TypeScriptCorePreferenceConstants.INSTALLED_TYPESCRIPT_PATH, ""); } @@ -152,8 +136,12 @@ private void initializeTypeScriptBuildPath(IEclipsePreferences node) { private void initializeTslintPreferences(IEclipsePreferences node, ITypeScriptRepository defaultRepository) { node.put(TypeScriptCorePreferenceConstants.TSLINT_STRATEGY, TslintSettingsStrategy.DisableTslint.name()); node.put(TypeScriptCorePreferenceConstants.TSLINT_USE_CUSTOM_TSLINTJSON_FILE, ""); - node.put(TypeScriptCorePreferenceConstants.TSLINT_EMBEDDED_TYPESCRIPT_ID, defaultRepository.getName()); - node.putBoolean(TypeScriptCorePreferenceConstants.TSLINT_USE_EMBEDDED_TYPESCRIPT, true); + if (defaultRepository != null) { + node.put(TypeScriptCorePreferenceConstants.TSLINT_EMBEDDED_TYPESCRIPT_ID, defaultRepository.getName()); + node.putBoolean(TypeScriptCorePreferenceConstants.TSLINT_USE_EMBEDDED_TYPESCRIPT, true); + } else { + node.putBoolean(TypeScriptCorePreferenceConstants.TSLINT_USE_EMBEDDED_TYPESCRIPT, false); + } node.put(TypeScriptCorePreferenceConstants.TSLINT_INSTALLED_TYPESCRIPT_PATH, ""); } diff --git a/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/repository/IDETypeScriptRepositoryManager.java b/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/repository/IDETypeScriptRepositoryManager.java index e04cdca9..48b76f8f 100644 --- a/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/repository/IDETypeScriptRepositoryManager.java +++ b/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/repository/IDETypeScriptRepositoryManager.java @@ -1,24 +1,69 @@ +/** + * Copyright (c) 2015-2016 Angelo ZERR. + * 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: + * Angelo Zerr - initial API and implementation + * Lorenzo Dalla Vecchia - loading of repositories from extension point + */ package ts.eclipse.ide.internal.core.repository; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtensionDelta; +import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IRegistryChangeEvent; +import org.eclipse.core.runtime.IRegistryChangeListener; +import org.eclipse.core.runtime.Platform; +import org.osgi.framework.Version; +import ts.eclipse.ide.core.TypeScriptCorePlugin; import ts.eclipse.ide.core.repository.IIDETypeScriptRepositoryManager; +import ts.eclipse.ide.internal.core.Trace; +import ts.repository.ITypeScriptRepository; import ts.repository.TypeScriptRepositoryManager; public class IDETypeScriptRepositoryManager extends TypeScriptRepositoryManager - implements IIDETypeScriptRepositoryManager { + implements IIDETypeScriptRepositoryManager, IRegistryChangeListener { public static final IIDETypeScriptRepositoryManager INSTANCE = new IDETypeScriptRepositoryManager(); + private static final String EXTENSION_TYPESCRIPT_REPOSITORIES = "typeScriptRepositories"; + private static final String PROJECT_LOC_TOKEN = "${project_loc:"; private static final String WORKSPACE_LOC_TOKEN = "${workspace_loc:"; private static final String END_TOKEN = "}"; + private boolean extensionRepositoriesLoaded; + private boolean registryListenerIntialized; + private final Map repositoriesByBaseDir; + + public IDETypeScriptRepositoryManager() { + super(); + this.extensionRepositoriesLoaded = false; + this.registryListenerIntialized = false; + this.repositoriesByBaseDir = new HashMap(); + } + @Override public String generateFileName(IResource resource, IProject project) { if (resource.getProject().equals(project)) { @@ -57,4 +102,153 @@ public IResource getResource(String path, IProject project) { IFile file = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(location); return file.exists() ? file : null; } + + @Override + public ITypeScriptRepository getDefaultRepository() { + loadExtensionRepositories(); + return super.getDefaultRepository(); + } + + @Override + public ITypeScriptRepository getRepository(String name) { + loadExtensionRepositories(); + return super.getRepository(name); + } + + @Override + public ITypeScriptRepository[] getRepositories() { + loadExtensionRepositories(); + return super.getRepositories(); + } + + private synchronized void loadExtensionRepositories() { + if (extensionRepositoriesLoaded) + return; + + // Immediately set the flag, as to ensure that this method is never + // called twice + extensionRepositoriesLoaded = true; + + Trace.trace(Trace.EXTENSION_POINT, "->- Loading .typeScriptRepositories extension point ->-"); + + IExtensionRegistry registry = Platform.getExtensionRegistry(); + IConfigurationElement[] cf = registry.getConfigurationElementsFor(TypeScriptCorePlugin.PLUGIN_ID, + EXTENSION_TYPESCRIPT_REPOSITORIES); + addExtensionRepositories(cf); + resetDefaultRepository(); + addRegistryListenerIfNeeded(); + + Trace.trace(Trace.EXTENSION_POINT, "-<- Done loading .typeScriptRepositories extension point -<-"); + } + + @Override + public void registryChanged(final IRegistryChangeEvent event) { + IExtensionDelta[] deltas = event.getExtensionDeltas(TypeScriptCorePlugin.PLUGIN_ID, + EXTENSION_TYPESCRIPT_REPOSITORIES); + if (deltas != null) { + synchronized (this) { + for (IExtensionDelta delta : deltas) { + IConfigurationElement[] cf = delta.getExtension().getConfigurationElements(); + if (delta.getKind() == IExtensionDelta.ADDED) { + addExtensionRepositories(cf); + } else { + removeExtensionRepositories(cf); + } + } + } + } + } + + private void addExtensionRepositories(IConfigurationElement[] cf) { + for (IConfigurationElement ce : cf) { + try { + File baseDir = computeActualBaseDir(ce); + ITypeScriptRepository repository = createRepository(baseDir); + synchronized (repositoriesByBaseDir) { + repositoriesByBaseDir.put(baseDir, repository); + } + Trace.trace(Trace.EXTENSION_POINT, " Loaded typeScriptRepositories: " + baseDir); + } catch (Throwable t) { + Trace.trace(Trace.SEVERE, " Error while loading typeScriptRepositories", t); + } + } + } + + private void removeExtensionRepositories(IConfigurationElement[] cf) { + for (IConfigurationElement ce : cf) { + try { + File baseDir = computeActualBaseDir(ce); + ITypeScriptRepository repository; + synchronized (repositoriesByBaseDir) { + repository = repositoriesByBaseDir.remove(baseDir); + } + if (repository != null) { + ITypeScriptRepository removedRepository = removeRepository(repository.getName()); + if (removedRepository != repository) { + Trace.trace(Trace.EXTENSION_POINT, "Unloaded typeScriptRepositories: " + baseDir); + } + } + } catch (Throwable t) { + Trace.trace(Trace.SEVERE, "Error while unloading typeScriptRepositories", t); + } + } + } + + private static File computeActualBaseDir(IConfigurationElement ce) throws IOException { + String bundleId = ce.getNamespaceIdentifier(); + File bundleDir = FileLocator.getBundleFile(Platform.getBundle(bundleId)); + if (!bundleDir.isDirectory()) { + throw new RuntimeException("Bundle location " + bundleDir + + " cannot contribute a TypeScript repository because it is not a directory"); + } + return new File(bundleDir, ce.getAttribute("baseDir")); + } + + private void resetDefaultRepository() { + + // Sort available repositories by version in decreasing order + List repositories = new ArrayList( + Arrays.asList(super.getRepositories())); + Collections.sort(repositories, new Comparator() { + + @Override + public int compare(ITypeScriptRepository repo1, ITypeScriptRepository repo2) { + Version v1 = extractVerion(repo1); + Version v2 = extractVerion(repo2); + return v2.compareTo(v1); + } + + private Version extractVerion(ITypeScriptRepository repo) { + try { + return Version.parseVersion(repo.getTypesScriptVersion()); + } catch (IllegalArgumentException e) { + return Version.emptyVersion; + } + } + }); + + // Reset the the default repository to the newest one available + if (repositories.isEmpty()) { + setDefaultRepository(null); + } else { + setDefaultRepository(repositories.get(0)); // first = newest + } + } + + private void addRegistryListenerIfNeeded() { + if (registryListenerIntialized) + return; + + IExtensionRegistry registry = Platform.getExtensionRegistry(); + registry.addRegistryChangeListener(this, TypeScriptCorePlugin.PLUGIN_ID); + registryListenerIntialized = true; + } + + public void initialize() { + + } + + public void destroy() { + Platform.getExtensionRegistry().removeRegistryChangeListener(this); + } }