diff --git a/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/core/builder/TypeScriptBuilder.java b/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/core/builder/TypeScriptBuilder.java index d8ad8519..b9ad89f8 100644 --- a/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/core/builder/TypeScriptBuilder.java +++ b/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/core/builder/TypeScriptBuilder.java @@ -25,6 +25,7 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.ProgressMonitorWrapper; import org.eclipse.core.runtime.Status; import ts.TypeScriptException; @@ -74,8 +75,8 @@ private void fullBuild(IIDETypeScriptProject tsProject, IProgressMonitor monitor for (int i = 0; i < tsContainers.length; i++) { ITsconfigBuildPath tsContainer = tsContainers[i]; /* - * try { IDETsconfigJson tsconfig = tsContainer.getTsconfig(); if - * (tsconfig == null || tsconfig.isCompileOnSave()) { + * try { IDETsconfigJson tsconfig = tsContainer.getTsconfig(); if (tsconfig == + * null || tsconfig.isCompileOnSave()) { * tsProject.getCompiler().compile(tsconfig, null); } } catch * (TypeScriptException e) { Trace.trace(Trace.SEVERE, * "Error while tsc compilation", e); } @@ -260,10 +261,22 @@ public boolean visit(IResourceDelta delta) throws CoreException { }); try { - tsProject.compileWithTsserver(updatedTsFiles, removedTsFiles, monitor); + if (updatedTsFiles.size() > 0 || removedTsFiles.size() > 0) { + tsProject.compileWithTsserver(updatedTsFiles, removedTsFiles, getSubMonitor(monitor)); + } } catch (TypeScriptException e) { throw new CoreException(new Status(IStatus.ERROR, TypeScriptCorePlugin.PLUGIN_ID, "Error while compiling with tsserver", e)); } } + + private IProgressMonitor getSubMonitor(IProgressMonitor monitor) { + // we need to use the parent SubMonitor otherwise SubMonitor#worked, etc doesn't work? + // See article + // https://medium.com/@jgwest/debugging-eclipse-subprogressmonitor-and-submonitor-0-work-completed-or-work-not-reported-f482c71cc85c + if (monitor instanceof ProgressMonitorWrapper) { + return getSubMonitor(((ProgressMonitorWrapper) monitor).getWrappedProgressMonitor()); + } + return monitor; + } } diff --git a/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/TypeScriptCoreMessages.java b/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/TypeScriptCoreMessages.java index 5c74b910..1ba9c699 100644 --- a/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/TypeScriptCoreMessages.java +++ b/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/TypeScriptCoreMessages.java @@ -37,6 +37,13 @@ public class TypeScriptCoreMessages extends NLS { // Launch public static String TypeScriptCompilerLaunchConfigurationDelegate_invalidBuildPath; + + // TypeScript Compiler builder + public static String IDETypeScriptProject_compile_task; + public static String IDETypeScriptProject_compile_collecting_step; + public static String IDETypeScriptProject_compile_collecting_file; + public static String IDETypeScriptProject_compile_compiling_step; + public static String IDETypeScriptProject_compile_compiling_file; public static ResourceBundle getResourceBundle() { try { diff --git a/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/TypeScriptCoreMessages.properties b/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/TypeScriptCoreMessages.properties index 783145a1..a7a7a8a1 100644 --- a/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/TypeScriptCoreMessages.properties +++ b/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/TypeScriptCoreMessages.properties @@ -20,4 +20,11 @@ tsconfig_cannot_use_compileOnSave_with_outFile_error=TypeScript file cannot be c tsconfig_cannot_use_compileOnSave_with_path_mapping_error=TypeScript file cannot be compiled on save because tsconfig.json uses path mapping features ("paths", "rootDirs"). If this is not intended, please set "buildOnSave" to "true" of your "{0}" file. # Launch -TypeScriptCompilerLaunchConfigurationDelegate_invalidBuildPath=The tsconfig file {0} does not exist for the compiler launch named {1}. \ No newline at end of file +TypeScriptCompilerLaunchConfigurationDelegate_invalidBuildPath=The tsconfig file {0} does not exist for the compiler launch named {1}. + +# TypeScript Compiler builder +IDETypeScriptProject_compile_task=TypeScript compilation +IDETypeScriptProject_compile_collecting_step=Collecting TypeScript files to compile... +IDETypeScriptProject_compile_collecting_file=Collect files for ''{0}'' +IDETypeScriptProject_compile_compiling_step=Compiling collected TypeScript files... +IDETypeScriptProject_compile_compiling_file=Compile file ''{0}''... diff --git a/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/resources/IDETypeScriptProject.java b/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/resources/IDETypeScriptProject.java index 88405f01..183ae0ce 100644 --- a/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/resources/IDETypeScriptProject.java +++ b/eclipse/ts.eclipse.ide.core/src/ts/eclipse/ide/internal/core/resources/IDETypeScriptProject.java @@ -16,8 +16,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; @@ -25,7 +27,10 @@ import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.SubMonitor; import org.eclipse.jface.text.IDocument; +import org.eclipse.osgi.util.NLS; import ts.TypeScriptException; import ts.TypeScriptNoContentAvailableException; @@ -51,6 +56,7 @@ import ts.eclipse.ide.core.utils.TypeScriptResourceUtil; import ts.eclipse.ide.core.utils.WorkbenchResourceUtil; import ts.eclipse.ide.internal.core.Trace; +import ts.eclipse.ide.internal.core.TypeScriptCoreMessages; import ts.eclipse.ide.internal.core.compiler.IDETypeScriptCompiler; import ts.eclipse.ide.internal.core.console.TypeScriptConsoleConnectorManager; import ts.eclipse.ide.internal.core.resources.jsonconfig.JsonConfigResourcesManager; @@ -376,19 +382,19 @@ protected ITypeScriptLint createTslint(File tslintFile, File tslintJsonFile, Fil @Override public void compileWithTsserver(List updatedTsFiles, List removedTsFiles, IProgressMonitor monitor) throws TypeScriptException { + SubMonitor subMonitor = SubMonitor.convert(monitor, TypeScriptCoreMessages.IDETypeScriptProject_compile_task, + 100); + List tsFilesToClose = new ArrayList<>(); try { List tsFilesToCompile = new ArrayList<>(); // Collect ts files to compile by using tsserver to retrieve // dependencies files. // It works only if tsconfig.json declares "compileOnSave: true". - if (collectTsFilesToCompile(updatedTsFiles, getClient(), tsFilesToCompile, false, monitor)) { - return; - } + collectTsFilesToCompile(updatedTsFiles, tsFilesToCompile, tsFilesToClose, getClient(), subMonitor); // Compile ts files with tsserver. - if (compileTsFiles(tsFilesToCompile, getClient(), monitor)) { - return; - } + compileTsFiles(tsFilesToCompile, getClient(), subMonitor); + if (removedTsFiles.size() > 0) { // ts files was removed, how to get referenced files which must // be recompiled (with errors)? @@ -397,54 +403,86 @@ public void compileWithTsserver(List updatedTsFiles, List removedT throw e; } catch (Exception e) { throw new TypeScriptException(e); + } finally { + for (IFile tsFile : tsFilesToClose) { + closeFile(tsFile); + } + subMonitor.done(); } } /** * Collect ts files to compile from the given ts files list. * - * @param tsFiles - * @param client + * @param updatedTsFiles + * list of TypeScript files which have changed. * @param tsFilesToCompile - * @param exclude - * @param monitor + * list of collected ts files to compile. + * @param tsFilesToClose + * list of ts files to close. + * @param client + * @param subMonitor * @throws Exception */ - private boolean collectTsFilesToCompile(List tsFiles, ITypeScriptServiceClient client, - List tsFilesToCompile, boolean exclude, IProgressMonitor monitor) throws Exception { - for (IFile tsFile : tsFiles) { - if (monitor.isCanceled()) { - return true; + private void collectTsFilesToCompile(List updatedTsFiles, List tsFilesToCompile, + List tsFilesToClose, ITypeScriptServiceClient client, SubMonitor subMonitor) throws Exception { + SubMonitor loopMonitor = subMonitor.split(50).setWorkRemaining(updatedTsFiles.size()); + loopMonitor.subTask(TypeScriptCoreMessages.IDETypeScriptProject_compile_collecting_step); + for (IFile tsFile : updatedTsFiles) { + if (loopMonitor.isCanceled()) { + throw new OperationCanceledException(); } String filename = WorkbenchResourceUtil.getFileName(tsFile); + loopMonitor + .subTask(NLS.bind(TypeScriptCoreMessages.IDETypeScriptProject_compile_collecting_file, filename)); if (!tsFilesToCompile.contains(filename)) { - collectTsFilesToCompile(filename, client, tsFilesToCompile, exclude); + collectTsFilesToCompile(filename, client, tsFilesToCompile, tsFilesToClose, loopMonitor); } + loopMonitor.worked(1); + // loopMonitor.split(1); } - return false; + // subMonitor.setWorkRemaining(50); } /** - * Collect ts files to compile from the given ts file name. + * Collect ts files to compile from the given TypeScript file. * * @param filename * @param client * @param tsFilesToCompile - * @param exclude + * @param tsFilesToClose + * @param monitor * @throws Exception */ private void collectTsFilesToCompile(String filename, ITypeScriptServiceClient client, - List tsFilesToCompile, boolean exclude) throws Exception { - // call tsserver compileOnSaveAffectedFileList to retrieve file - // dependencies of the given filename - List affectedProjects = client - .compileOnSaveAffectedFileList(filename).get(5000, TimeUnit.MILLISECONDS); - for (CompileOnSaveAffectedFileListSingleProject affectedProject : affectedProjects) { - List affectedTsFilenames = affectedProject.getFileNames(); - for (String affectedFilename : affectedTsFilenames) { - if (!tsFilesToCompile.contains(affectedFilename) && !(exclude && filename.equals(affectedFilename))) { - tsFilesToCompile.add(affectedFilename); + List tsFilesToCompile, List tsFilesToClose, IProgressMonitor monitor) throws Exception { + while (!monitor.isCanceled()) { + try { + // When tsserver is not started, it takes time, we try to collect TypeScript + // files every time and stop the search if user stops the builder. + List affectedProjects = client + .compileOnSaveAffectedFileList(filename).get(5000, TimeUnit.MILLISECONDS); + if (affectedProjects.size() == 0 && getOpenedFile(filename) == null) { + // Case when none TypeScript files are opened. + // In this case, compileOnSaveAffectedFileList returns null, the tsserver needs + // having just one opened TypeScript file + // in order to compileOnSaveAffectedFileList returns the well list. + IFile tsFile = WorkbenchResourceUtil.findFileFromWorkspace(filename); + openFile(tsFile, null); + tsFilesToClose.add(tsFile); + affectedProjects = client.compileOnSaveAffectedFileList(filename).get(5000, TimeUnit.MILLISECONDS); } + for (CompileOnSaveAffectedFileListSingleProject affectedProject : affectedProjects) { + List affectedTsFilenames = affectedProject.getFileNames(); + for (String affectedFilename : affectedTsFilenames) { + if (!tsFilesToCompile.contains(affectedFilename)) { + tsFilesToCompile.add(affectedFilename); + } + } + } + return; + } catch (TimeoutException e) { + // tsserver is not initialized, retry again... } } } @@ -454,28 +492,32 @@ private void collectTsFilesToCompile(String filename, ITypeScriptServiceClient c * * @param tsFilesToCompile * @param client - * @param monitor - * @return true if process must be stopped and false otherwise. + * @param subMonitor * @throws Exception */ - private boolean compileTsFiles(List tsFilesToCompile, ITypeScriptServiceClient client, - IProgressMonitor monitor) throws Exception { + private void compileTsFiles(List tsFilesToCompile, ITypeScriptServiceClient client, SubMonitor subMonitor) + throws Exception { + SubMonitor loopMonitor = subMonitor.newChild(50).setWorkRemaining(tsFilesToCompile.size());// subMonitor.split(50).setWorkRemaining(tsFilesToCompile.size()); + loopMonitor.subTask(TypeScriptCoreMessages.IDETypeScriptProject_compile_compiling_step); for (String filename : tsFilesToCompile) { - if (monitor.isCanceled()) { - return true; - } try { + if (loopMonitor.isCanceled()) { + throw new OperationCanceledException(); + } + loopMonitor.subTask( + NLS.bind(TypeScriptCoreMessages.IDETypeScriptProject_compile_compiling_file, filename)); compileTsFile(filename, client); + loopMonitor.worked(1); + // loopMonitor.split(1); } catch (ExecutionException e) { if (e.getCause() instanceof TypeScriptNoContentAvailableException) { // Ignore "No content available" error. - } - else { + } else { throw e; } } } - return false; + // subMonitor.setWorkRemaining(100); } /** @@ -493,7 +535,7 @@ private void compileTsFile(String filename, ITypeScriptServiceClient client) thr if (tsFile != null) { // Delete TypeScript error marker TypeScriptResourceUtil.deleteTscMarker(tsFile); - // Add TypeScript error marker if there error errors. + // Add TypeScript error marker if there are errors. DiagnosticEventBody event = client.syntacticDiagnosticsSync(filename, true).get(5000, TimeUnit.MILLISECONDS); addMarker(tsFile, event);