/* * $Id$ * * Copyright (c) 2004-2005 by the TeXlapse Team. * 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 net.sourceforge.texlipse.builder; import java.io.ByteArrayInputStream; import java.io.File; import java.util.Map; import net.sourceforge.texlipse.TexlipsePlugin; import net.sourceforge.texlipse.model.TexDocumentModel; import net.sourceforge.texlipse.properties.TexlipseProperties; import net.sourceforge.texlipse.texparser.LatexParserUtils; import net.sourceforge.texlipse.viewer.ViewerManager; 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.IncrementalProjectBuilder; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.text.IDocument; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.texteditor.ITextEditor; /** * Builder class interfacing with Eclipse API. * * @author Kimmo Karlsson * @author Boris von Loesch */ public class TexlipseBuilder extends IncrementalProjectBuilder { // Use fully qualified id as the builder id. // Note: this requires the plugin to have the same name as the main java package. public static final String BUILDER_ID = TexlipseBuilder.class.getName(); // marker type for builder problems public static final String MARKER_TYPE = TexlipseProperties.PACKAGE_NAME + ".builderproblem"; // marker type for builder layout warnings public static final String LAYOUT_WARNING_TYPE = TexlipseProperties.PACKAGE_NAME + ".layoutproblem"; // minimum number of characters that a valid latex document can have private static final int validDocumentLimit = 10; //Put this in your document to prevent it from building private static final String NO_PARTIAL_BUILD = "%##noBuild"; /** * Build the project. * * @see IncrementalProjectBuilder.build */ @Override protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException { final IProject project = getProject(); final ProjectFileTracking fileTracking = new ProjectFileTracking(project); final OutputFileManager fileManager = new OutputFileManager(project, fileTracking); Object rebuild = TexlipseProperties.getSessionProperty(project, TexlipseProperties.FORCED_REBUILD); // Wait for all scheduled parser jobs, since they could change relevant // session properties for (Job parser : Job.getJobManager().find(TexDocumentModel.PARSER_FAMILY)) { int state = parser.getState(); if (state == Job.WAITING || state == Job.SLEEPING) { // If waiting, run immediately parser.cancel(); parser.schedule(); } try { parser.join(); } catch (InterruptedException e) { } } if (rebuild == null && fileManager.isUpToDate()) { return null; } BuilderRegistry.clearConsole(); Object s = TexlipseProperties.getProjectProperty(project, TexlipseProperties.PARTIAL_BUILD_PROPERTY); if (s != null) { partialBuild(project, fileManager, monitor); } else { buildFile(project, null, fileManager, monitor); } TexlipseProperties.setSessionProperty(project, TexlipseProperties.FORCED_REBUILD, null); return null; } /** * Clean the temporary files. * * @see IncrementalProjectBuilder.clean */ @Override protected void clean(IProgressMonitor monitor) throws CoreException { IProject project = getProject(); final ProjectFileTracking fileTracking = new ProjectFileTracking(project); final OutputFileManager fileManager = new OutputFileManager(project, fileTracking); BuilderRegistry.clearConsole(); // reset session variables TexlipseProperties.setSessionProperty(project, TexlipseProperties.SESSION_LATEX_RERUN, null); TexlipseProperties.setSessionProperty(project, TexlipseProperties.SESSION_BIBTEX_RERUN, null); TexlipseProperties.setSessionProperty(project, TexlipseProperties.BIBFILES_CHANGED, null); // check main file String mainFile = TexlipseProperties.getProjectProperty(project, TexlipseProperties.MAINFILE_PROPERTY); if (mainFile == null || mainFile.length() == 0) { // main tex file not set -> nothing builded -> nothing to clean return; } fileManager.cleanTempFiles(monitor); fileManager.cleanOutputFile(monitor); monitor.subTask(TexlipsePlugin.getResourceString("builderSubTaskCleanMarkers")); this.deleteMarkers(project); project.refreshLocal(IProject.DEPTH_INFINITE, monitor); monitor.done(); } /** * Perform a partial build. * * @param project current project * @param fileManager output file manager instance * @param monitor progress monitor * @throws CoreException if an error occurs */ private void partialBuild(final IProject project, final OutputFileManager fileManager, final IProgressMonitor monitor) throws CoreException { IEditorPart part = TexlipsePlugin.getCurrentWorkbenchPage().getActiveEditor(); ITextEditor editor = null; if (part instanceof ITextEditor) editor = (ITextEditor) part; // find out the file that should be built partially IResource res = (IResource) part.getEditorInput().getAdapter(IResource.class); if (res == null || res.getType() != IResource.FILE || !res.getProject().equals(project)) { // No file is selected, so user must be browsing // with the navigator. Don't build anything yet. return; } String resourceName = res.getName(); int extIndex = resourceName.lastIndexOf('.'); String ext = resourceName.substring(extIndex+1); IDocument doc = editor.getDocumentProvider().getDocument(part.getEditorInput()); //load settings, if changed on disk if (TexlipseProperties.isProjectPropertiesFileChanged(project)) { TexlipseProperties.loadProjectProperties(project); } IFile file = project.getFile(res.getProjectRelativePath()); String content = doc.get(); if (content.indexOf(NO_PARTIAL_BUILD) >= 0) { //Do not build this file or anything else return; } else if (resourceName.equals(TexlipseProperties.getProjectProperty(project, TexlipseProperties.MAINFILE_PROPERTY)) || (!ext.equals("tex") && !ext.equals("ltx"))) { // main file can't be built partially // also, bib file changes need full build TexlipseProperties.setSessionProperty(project, TexlipseProperties.PARTIAL_BUILD_FILE, null); buildFile(project, null, fileManager, monitor); return; } else if (LatexParserUtils.findCommand(content, "\\documentclass", 0) != -1 || LatexParserUtils.findCommand(content, "\\documentstyle", 0) != -1 || LatexParserUtils.findBeginEnvironment(content, "document", 0) != null) { // A complete tex file (just build it) TexlipseProperties.setSessionProperty(project, TexlipseProperties.PARTIAL_BUILD_FILE, file); buildFile(project, file, fileManager, monitor); return; } String tempFileContents = getTempFileContents(file, project, monitor); if (tempFileContents == null) { //Can not create a valid tmp file return; } //The temp file should be in the main folder IContainer folder = TexlipseProperties.getProjectSourceDir(project); IFile tmpFile = folder.getFile(new Path("tempPartial00000.tex")); TexlipseProperties.setSessionProperty(project, TexlipseProperties.PARTIAL_BUILD_FILE, tmpFile); if (tmpFile == null) { throw new CoreException(TexlipsePlugin.stat("Can't create temp file")); } // write temp file ByteArrayInputStream bar = new ByteArrayInputStream(tempFileContents.getBytes()); if (tmpFile.exists()) { tmpFile.setContents(bar, true, false, monitor); } else { tmpFile.create(bar, true, monitor); } tmpFile.setDerived(true); // build temp file buildFile(project, tmpFile, fileManager, monitor); } /** * Generate temp file contents. Therefore it includes * the full preamble + \include{file} + bibtex settings * * @param file * @param project * @param monitor * @return The content of the tmp file or null if no preamble was found * @throws CoreException */ private String getTempFileContents(IFile file, IProject project, final IProgressMonitor monitor) throws CoreException { // get information from the main file String preamble = (String) TexlipseProperties.getSessionProperty(project, TexlipseProperties.PREAMBLE_PROPERTY); if (preamble == null) { BuilderRegistry.printToConsole(TexlipsePlugin.getResourceString("builderNoPreambleFound")); return null; } String bibsty = (String) TexlipseProperties.getSessionProperty(project, TexlipseProperties.BIBSTYLE_PROPERTY); String[] bibli = (String[]) TexlipseProperties.getSessionProperty(project, TexlipseProperties.BIBFILE_PROPERTY); Boolean biblatexMode = (Boolean) TexlipseProperties.getSessionProperty(project, TexlipseProperties.SESSION_BIBLATEXMODE_PROPERTY); Boolean localBib = (Boolean) TexlipseProperties.getSessionProperty(project, TexlipseProperties.SESSION_BIBLATEXLOCALBIB_PROPERTY); // generate the file contents //StringBuffer sb = readFile(file.getContents(), monitor); StringBuilder sb = new StringBuilder ("\\input{"); String name = ViewerManager.resolveRelativePath(TexlipseProperties.getProjectSourceDir(project).getProjectRelativePath(), file.getProjectRelativePath()); name = name.substring(0, name.lastIndexOf('.') + 1); boolean ws = false; if (name.indexOf(' ') >= 0) { sb.append('"'); ws = true; } //In windows convert the bs to slashes for (int i=0; i<name.length() - 1; i++){ char c = name.charAt(i); if (c == File.separatorChar) sb.append('/'); else if (c == ' ') { sb.append("\\space "); } else sb.append(c); } //sb.append(name.substring(0, name.length() - file.getFileExtension().length() - 1)); if (ws) { sb.append('"'); } sb.append("}\n"); if (biblatexMode == null) { if (bibsty != null) { sb.append("\\bibliographystyle{"); sb.append(bibsty); sb.append("}\n"); } if (bibli != null && bibli.length > 0) { sb.append("\\bibliography{"); for (int i = 0; i < bibli.length-1; i++) { int ext = bibli[i].lastIndexOf('.'); if (ext >= 0) sb.append(bibli[i].substring(0, ext)); else sb.append(bibli[i]); sb.append(','); } if (bibli.length > 1 || !bibli[0].equals(".bib")) { // parser bugfix int ext = bibli[bibli.length-1].lastIndexOf('.'); if (ext >= 0) sb.append(bibli[bibli.length-1].substring(0, ext)); else sb.append(bibli[bibli.length-1]); } sb.append("}\n"); } } else { // Only add bibliography if it is not already included in the current file. if (localBib == null) { sb.append("\\printbibliography"); } } sb.append("\n\\end{document}\n"); return preamble + '\n' + sb.toString(); } /** * Builds a document * * @param project the current project * @param resource the file to build, if <code>null</code> build main document * @param fileManager output file manager instance * @param monitor progress monitor * @throws CoreException if an error occurs */ private void buildFile(final IProject project, IFile resource, final OutputFileManager fileManager, IProgressMonitor monitor) throws CoreException { //load settings, if changed on disk if (TexlipseProperties.isProjectPropertiesFileChanged(project)) { TexlipseProperties.loadProjectProperties(project); } Builder builder = null; try { builder = checkBuilderSettings(project); } catch (CoreException e) { // can't get builder, so can't build. error reported to the console return; } if (resource == null) { //Full build try { // check settings resource = (IFile) checkFileSettings(project, monitor); } catch (CoreException e) {} if (resource == null) { // silent fail. the file doesn't contain enough tags yet return; } } // number 100 is just some kind of guess of how much work there is monitor.beginTask(TexlipsePlugin.getResourceString("builderSubTaskBuild"), 100); this.deleteMarkers(project); monitor.worked(1); // reset builder instance to startable state builder.reset(monitor); // run file processes before build (e.g. moving temp files in) fileManager.setCurrentSourceFile(resource); fileManager.performBeforeBuild(monitor); // start the build try { builder.build(resource); } catch (BuilderCoreException e) { } // run file processes after build (e.g. moving files out) fileManager.performAfterBuild(monitor); monitor.done(); } /** * Check that the filename settings are correct. * * @param project the current project * @param monitor progress monitor * @return project's main file * @throws CoreException if some setting is not correct */ private IResource checkFileSettings(final IProject project, IProgressMonitor monitor) throws CoreException { String mainFile = TexlipseProperties.getProjectProperty(project, TexlipseProperties.MAINFILE_PROPERTY); if (mainFile == null || mainFile.length() == 0) { // maybe not a good idea to report as error, at least when in java-project //throw new CoreException(TexlipsePlugin.stat("Main .tex -file name not set.")); BuilderRegistry.printToConsole(TexlipsePlugin.getResourceString("builderErrorMainFileNotSet").replaceAll("%s", project.getName())); } String outputFile = TexlipseProperties.getProjectProperty(project, TexlipseProperties.OUTPUTFILE_PROPERTY); if (outputFile == null || outputFile.length() == 0) { BuilderRegistry.printToConsole(TexlipsePlugin.getResourceString("builderErrorOutputFileNotSet").replaceAll("%s", project.getName())); throw new CoreException(TexlipsePlugin.stat("Project output file name not set.")); } IFile resource = TexlipseProperties.getProjectSourceFile(project); if (resource == null || !resource.exists()) { BuilderRegistry.printToConsole(TexlipsePlugin.getResourceString("builderErrorMainFileNotFound").replaceAll("%s", project.getName())); throw new CoreException(TexlipsePlugin.stat("Main .tex -file not found.")); } if (resource.getRawLocation().toFile().length() < validDocumentLimit) { return null; } return resource; } /** * Update builder, if necessary, and check that the builder settings are correct. * * @param project the current project * @return builder for this project * @throws CoreException if some setting is not correct */ private Builder checkBuilderSettings(IProject project) throws CoreException { String format = TexlipseProperties.getProjectProperty(project, TexlipseProperties.OUTPUT_FORMAT); if (format == null || format.length() == 0) { BuilderRegistry.printToConsole(TexlipsePlugin.getResourceString("builderErrorOutputFormatNotSet").replaceAll("%s", project.getName())); throw new CoreException(TexlipsePlugin.stat("Project output file format not set.")); } String str = TexlipseProperties.getProjectProperty(project, TexlipseProperties.BUILDER_NUMBER); if (str == null) { BuilderRegistry.printToConsole(TexlipsePlugin.getResourceString("builderErrorOutputBuilderNotSet").replaceAll("%s", project.getName())); throw new CoreException(TexlipsePlugin.stat("No builder selected.")); } int number = 0; try { number = Integer.parseInt(str); } catch (NumberFormatException e) { } Builder builder = BuilderRegistry.get(number); if (builder instanceof AdaptableBuilder) { ((AdaptableBuilder) builder).updateBuilder(project); } if (builder == null) { BuilderRegistry.printToConsole(TexlipsePlugin.getResourceString("builderErrorBuilderNumberNotSet").replaceAll("%s", project.getName()).replaceAll("%f", format).replaceAll("%i", number+"")); throw new CoreException(TexlipsePlugin.stat("Builder (#" + number + ") for " + format + " output format not configured.")); } else if (!builder.isValid()) { BuilderRegistry.printToConsole(TexlipsePlugin.getResourceString("builderErrorBuilderNumberInvalid").replaceAll("%s", project.getName()).replaceAll("%f", format).replaceAll("%i", number+"")); throw new CoreException(TexlipsePlugin.stat("Builder (#" + number + ") for " + format + " output format has an invalid configuration. Please check" + "if paths to builder programs are set up correctly.")); } return builder; } /** * Delete old build errors and layout markers from project * @param project * @throws CoreException */ protected void deleteMarkers (IProject project) throws CoreException{ project.deleteMarkers(MARKER_TYPE, false, IResource.DEPTH_INFINITE); project.deleteMarkers(LAYOUT_WARNING_TYPE, false, IResource.DEPTH_INFINITE); } }