/*
* $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.model;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import net.sourceforge.texlipse.TexlipsePlugin;
import net.sourceforge.texlipse.bibparser.BibParser;
import net.sourceforge.texlipse.editor.TexDocumentParseException;
import net.sourceforge.texlipse.editor.TexEditor;
import net.sourceforge.texlipse.outline.TexOutlinePage;
import net.sourceforge.texlipse.outline.TexProjectOutline;
import net.sourceforge.texlipse.properties.TexlipseProperties;
import net.sourceforge.texlipse.texparser.LatexRefExtractingParser;
import net.sourceforge.texlipse.texparser.TexParser;
import net.sourceforge.texlipse.treeview.views.TexOutlineTreeView;
import net.sourceforge.texlipse.builder.KpsewhichRunner;
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.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ILock;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.SubStatusLineManager;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.progress.WorkbenchJob;
/**
* LaTeX document model. Handles parsing of the document and updating
* the outline, folding and content assist datastructures.
*
* @author Taavi Hupponen
* @author Oskar Ojala
* @author Boris von Loesch
*/
public class TexDocumentModel implements IDocumentListener {
public static final String PARSER_FAMILY = "TexDocument Parser";
/**
* Job for performing the parsing in a background thread.
* When parsing is done schedules the PostParseJob, which
* updates the ui stuff. waits for the PostParseJob to finish.
*
* Monitor is polled often to detect cancellation.
*
* @author Taavi Hupponen
*
*/
private class ParseJob extends Job {
/**
* @param name name of the job
*/
public ParseJob(String name) {
super(name);
}
/**
* @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
*/
protected IStatus run(IProgressMonitor monitor) {
try {
// before parsing stuff, only takes time when run the first time
if (bibContainer == null) {
createReferenceContainers();
}
pollCancel(monitor);
// parsing
ArrayList<OutlineNode> rootNodes;
try {
rootNodes = doParse(monitor);
} catch (TexDocumentParseException e1) {
return Status.CANCEL_STATUS;
}
pollCancel(monitor);
// handling of parse results
postParseJob.setRootNodes(rootNodes);
postParseJob.schedule();
try {
postParseJob.join();
} catch (InterruptedException e2) {
return Status.CANCEL_STATUS;
}
// return parse status etc.
IStatus result = postParseJob.getResult();
// parsing ok
if (result != null && result.equals(Status.OK_STATUS)) {
// cancel check must be here before setDirty(false)!
try {
lock.acquire();
pollCancel(monitor);
setDirty(false);
} finally {
lock.release();
}
return result;
}
// parsing not ok
return Status.CANCEL_STATUS;
} catch (Exception e) {
return Status.CANCEL_STATUS;
}
}
@Override
public boolean belongsTo(Object family) {
return family.equals(PARSER_FAMILY);
}
}
/**
* Job for updating the ui after parsing. Runs in the ui thread.
*
* Monitor is polled often to detect cancellation.
*
* @author Taavi Hupponen
*/
private class PostParseJob extends WorkbenchJob {
private ArrayList<OutlineNode> rootNodes;
private List<OutlineNode> fullOutlineNodes;
/**
*
* @param name name of the job
*/
public PostParseJob(String name) {
super(name);
}
/**
* @param rootNodes
*/
public void setRootNodes(ArrayList<OutlineNode> rootNodes) {
this.rootNodes = rootNodes;
}
/**
* @param rootNodes
*/
public void setFONodes(List<OutlineNode> rootNodes) {
this.fullOutlineNodes = rootNodes;
}
/**
* @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
*/
public IStatus runInUIThread(IProgressMonitor monitor) {
try {
//long time = System.currentTimeMillis();
updateDocumentPositions(rootNodes, monitor);
//System.out.println("updateDocPos: " + (System.currentTimeMillis() - time));
pollCancel(monitor);
//time = System.currentTimeMillis();
editor.updateCodeFolder(rootNodes, monitor);
//System.out.println("updateCodeFolder: " + (System.currentTimeMillis() - time));
pollCancel(monitor);
if (editor.getOutlinePage() != null) {
//time = System.currentTimeMillis();
editor.getOutlinePage().update(outlineInput);
//System.out.println("updateOutline: " + (System.currentTimeMillis() - time));
}
//Update FullOutline
if (fullOutlineNodes != null) {
pollCancel(monitor);
if (editor.getFullOutline() != null) {
//time = System.currentTimeMillis();
//createOutlineInput(fullOutlineNodes, monitor);
editor.getFullOutline().update(new TexOutlineInput(new ArrayList<OutlineNode>(fullOutlineNodes)));
//System.out.println("updateFullOutline: " + (System.currentTimeMillis() - time));
}
}
return Status.OK_STATUS;
} catch (Exception e) {
// npe when exiting eclipse and saving
return Status.CANCEL_STATUS;
}
}
}
private TexEditor editor;
private TexParser parser;
private TexProjectOutline projectOutline;
private TexOutlineInput outlineInput;
private ReferenceContainer bibContainer;
private ReferenceContainer labelContainer;
private TexCommandContainer commandContainer;
private ReferenceManager refMana;
private boolean firstRun = true;
// used to synchronize ParseJob rescheduling
private static ILock lock = Job.getJobManager().newLock();
private boolean isDirty;
private ParseJob parseJob;
private PostParseJob postParseJob;
// preferences
private int parseDelay;
private boolean autoParseEnabled;
private boolean sectionCheckEnabled;
/**
* Constructs a new document model.
*
* @param editor The editor this model is associated to.
*/
public TexDocumentModel(TexEditor editor) {
this.editor = editor;
this.isDirty = true;
// initialize jobs
parseJob = new ParseJob("Parsing");
postParseJob = new PostParseJob("Updating");
parseJob.setPriority(Job.DECORATE);
postParseJob.setPriority(Job.DECORATE);
// get preferences
this.parseDelay = TexlipsePlugin.getDefault().getPreferenceStore().getInt(TexlipseProperties.AUTO_PARSING_DELAY);
this.autoParseEnabled = TexlipsePlugin.getDefault().getPreferenceStore().getBoolean(TexlipseProperties.AUTO_PARSING);
this.sectionCheckEnabled = TexlipsePlugin.getDefault().getPreferenceStore().getBoolean(TexlipseProperties.SECTION_CHECK);
// add preference change listener
TexlipsePlugin.getDefault().getPreferenceStore()
.addPropertyChangeListener(new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
String property = event.getProperty();
if (TexlipseProperties.AUTO_PARSING.equals(property)) {
autoParseEnabled = TexlipsePlugin.getDefault().getPreferenceStore().getBoolean(TexlipseProperties.AUTO_PARSING);
} else if (TexlipseProperties.AUTO_PARSING_DELAY.equals(property)) {
parseDelay = TexlipsePlugin.getDefault().getPreferenceStore().getInt(TexlipseProperties.AUTO_PARSING_DELAY);
} else if (TexlipseProperties.SECTION_CHECK.equals(property)) {
sectionCheckEnabled = TexlipsePlugin.getDefault().getPreferenceStore().getBoolean(TexlipseProperties.SECTION_CHECK);
}
}
});
}
/**
* Initializes the model. Should be called immediately after constructing
* an instance, otherwise parsing may fail.
*/
public void initializeModel() {
MarkerHandler.getInstance().clearErrorMarkers(editor);
MarkerHandler.getInstance().clearTaskMarkers(editor);
createReferenceContainers();
}
/**
* Cancels possibly running parseJob and schedules it to run again
* immediately.
*
* Called when new outline is created, so it is quite likely that there
* is no parseJob running.
*
* If parseJob were running we could maybe use isDirty to figure out
* if it would be smarter to wait for the running parseJob.
*/
public void updateNow() {
parseJob.cancel();
parseJob.schedule();
}
/**
* Called from TexEditor.getAdapter(). If uptodate outline input is
* found the outline is updated with it.
*
* If current outline is not uptodate, parsing job is started and
* eventually outline is updated with fresh input.
*/
public void updateOutline() {
if (!isDirty()) {
this.editor.getOutlinePage().update(this.outlineInput);
} else {
this.updateNow();
}
}
/**
* Returns the reference (label and BibTeX) for this model
* (ie. project).
*
* @return Returns the refMana.
*/
public ReferenceManager getRefMana() {
if (refMana == null) {
if (bibContainer == null) createReferenceContainers();
refMana = new ReferenceManager(bibContainer,
labelContainer,
commandContainer);
}
return refMana;
}
/**
* Returns whether current OutlineInput is dirty, i.e. if the
* document has been changed after latest parsing.
*
* @return true if document is dirty
*/
public synchronized boolean isDirty() {
return this.isDirty;
}
/**
* Does nothing atm.
*
* @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
*/
public void documentAboutToBeChanged(DocumentEvent event) {
}
/**
* Called when document changes. Marks the document dirty and
* schedules the parsing job.
*
* When we return from here this.isDirty must remain true
* until new parseJob has finished.
*
* If the previous parseJob is not cancelled immediately
* (parseJob.cancel() returns false) a smarter way to
* calculate the next scheduling delay could be used.
* However we should not spend much time in documentChanged()
*/
public void documentChanged(DocumentEvent event) {
// set isDirty true and prevent possibly running parseJob from
// changing it back to false
// order of acquire, cancel and setDirty matters!
try {
lock.acquire();
parseJob.cancel();
this.setDirty(true);
} finally {
lock.release();
}
// if parseJob was running, now it is either cancelled or it will
// be before it tries setDirty(false)
// inform outline that model is dirty
TexOutlinePage outline = editor.getOutlinePage();
if (outline != null) {
editor.getOutlinePage().modelGotDirty();
}
TexOutlineTreeView fullOutline = editor.getFullOutline();
if (fullOutline != null) {
fullOutline.modelGotDirty();
}
// reschedule parsing with delay
if (autoParseEnabled) {
parseJob.schedule(parseDelay);
}
}
/**
* Creates if not exist the ProjectOutline
*
*/
private void createProjectOutline() {
IProject project = getCurrentProject();
if (project == null) return;
Object projectSessionOutLine = TexlipseProperties.getSessionProperty(project,
TexlipseProperties.SESSION_PROJECT_FULLOUTLINE);
if (projectSessionOutLine != null)
projectOutline = (TexProjectOutline) projectSessionOutLine;
else {
projectOutline = new TexProjectOutline(getCurrentProject());
TexlipseProperties.setSessionProperty(project, TexlipseProperties.SESSION_PROJECT_FULLOUTLINE,
projectOutline);
}
}
/**
* Parses the LaTeX-document and adds error markers if there were any
* errors. Throws <code>TexDocumentParseException</code> if there were
* fatal parse errors that prohibit building an outline.
*
* @param monitor
*
* @throws TexDocumentParseException
*/
private ArrayList<OutlineNode> doParse(IProgressMonitor monitor) throws TexDocumentParseException {
if (this.parser == null) {
this.parser = new TexParser(editor.getDocumentProvider().getDocument(editor.getEditorInput()));
}
if (projectOutline == null) {
createProjectOutline();
}
try {
parser.parseDocument(sectionCheckEnabled);
} catch (IOException e) {
TexlipsePlugin.log("Can't read file.", e);
throw new TexDocumentParseException(e);
}
pollCancel(monitor);
List<ParseErrorMessage> errors = parser.getErrors();
List<ParseErrorMessage> tasks = parser.getTasks();
MarkerHandler marker = MarkerHandler.getInstance();
// somewhat inelegantly ensures that errors marked in createProjectDatastructs()
// aren't removed immediately
if (!firstRun) {
marker.clearErrorMarkers(editor);
marker.clearTaskMarkers(editor);
} else {
firstRun = false;
}
if (editor.getProject() != null && editor.getFullOutline() != null) {
IResource res = (IResource) editor.getEditorInput().getAdapter(IResource.class);
String fileName = res.getProjectRelativePath().toString();
projectOutline.addOutline(parser.getOutlineTree(), fileName);
List<OutlineNode> fo = projectOutline.getFullOutline();
postParseJob.setFONodes(fo);
} else {
postParseJob.setFONodes(null);
}
pollCancel(monitor);
processIncludes(parser.getInputs(), editor.getEditorInput());
if (errors.size() > 0) {
marker.createErrorMarkers(editor, errors);
}
if (tasks.size() > 0) {
marker.createTaskMarkers(editor, tasks);
}
if (parser.isFatalErrors()) {
throw new TexDocumentParseException("Fatal errors in file, parsing aborted.");
}
updateReferences(monitor);
List<DocumentReference> cites = parser.getCites();
List<DocumentReference> bibErrors = null;
for (DocumentReference cite : cites) {
if (!bibContainer.binTest(cite.getKey())) {
if (bibErrors == null) bibErrors = new ArrayList<DocumentReference>();
bibErrors.add(cite);
}
}
if (bibErrors != null) {
marker.createReferencingErrorMarkers(editor, bibErrors);
}
List<DocumentReference> refs = parser.getRefs();
List<DocumentReference> refErrors = null;
for (DocumentReference ref : refs) {
if (!labelContainer.binTest(ref.getKey())) {
if (refErrors == null) refErrors = new ArrayList<DocumentReference>();
refErrors.add(ref);
}
}
if (refErrors != null) {
marker.createReferencingErrorMarkers(editor, refErrors);
}
return this.parser.getOutlineTree();
}
/**
* Traverses the OutlineNode tree and adds a Position for each
* node to Document.
*
* Also adds the nodes to type lists of the OutlineInput and
* calculates the tree depth.
*
* Old Positions are removed before adding new ones.
*
* @param rootNodes
* @param monitor monitor for the job calling this method
*/
private void updateDocumentPositions(List<OutlineNode> rootNodes, IProgressMonitor monitor) {
TexOutlineInput newOutlineInput = new TexOutlineInput(rootNodes);
IDocument document = editor.getDocumentProvider().getDocument(editor.getEditorInput());
// remove previous positions
try {
document.removePositionCategory("__outline");
} catch (BadPositionCategoryException bpce) {
// do nothing, the category will be added again next, it does not exists the first time
}
document.addPositionCategory("__outline");
pollCancel(monitor);
// add new positions for nodes and their children
int maxDepth = 0;
for (Iterator<OutlineNode> iter = rootNodes.iterator(); iter.hasNext(); ) {
OutlineNode node = iter.next();
int localDepth = addNodePosition(node, document, 0, newOutlineInput);
if (localDepth > maxDepth) {
maxDepth = localDepth;
}
pollCancel(monitor);
}
pollCancel(monitor);
// set the new outline input
newOutlineInput.setTreeDepth(maxDepth);
this.outlineInput = newOutlineInput;
}
/**
* Handles a single node when traversing the outline tree. Used
* recursively.
*
* @param node
* @param document
* @param parentDepth
* @param newOutlineInput
* @return
*/
private int addNodePosition(OutlineNode node, IDocument document,
int parentDepth, TexOutlineInput newOutlineInput) {
// add the Document position
int beginOffset = 0;
int length = 0;
Position position = null;
try {
beginOffset = document.getLineOffset(node.getBeginLine() - 1);
if (node.getEndLine() -1 == document.getNumberOfLines())
length = document.getLength() - beginOffset;
else
length = document.getLineOffset(node.getEndLine() - 1) - beginOffset;
position = new Position(beginOffset, length);
document.addPosition("__outline", position);
} catch (BadLocationException bpe) {
throw new OperationCanceledException();
} catch (BadPositionCategoryException bpce) {
throw new OperationCanceledException();
}
node.setPosition(position);
// add node to outline input
newOutlineInput.addNode(node);
// iterate through the children
List<OutlineNode> children = node.getChildren();
int maxDepth = parentDepth + 1;
if (children != null) {
for (Iterator<OutlineNode> iter = children.iterator(); iter.hasNext();) {
int localDepth = addNodePosition(iter.next(), document, parentDepth + 1, newOutlineInput);
if (localDepth > maxDepth) {
maxDepth = localDepth;
}
}
}
return maxDepth;
}
/**
* Updates the settings for the BibLaTeX package. If this is not the initial run,
* checks if settings have changed from previous parse job and, if applicable, sets a
* notification flag for the builder that something has changed.
*
* @param project the current project
* @param biblatexMode true, if biblatex package was found by parser
* @param biblatexBackend database backend detected by parser, or null
* @param init whether this is the initial run
*/
private void updateBiblatex(IProject project, boolean biblatexMode,
String biblatexBackend, boolean init) {
if (!init) {
Boolean oldBLMode = (Boolean) TexlipseProperties.getSessionProperty(project,
TexlipseProperties.SESSION_BIBLATEXMODE_PROPERTY);
String oldBackend = (String) TexlipseProperties.getSessionProperty(project,
TexlipseProperties.SESSION_BIBLATEXBACKEND_PROPERTY);
boolean bibChanged;
if (biblatexMode) {
if (oldBLMode != null) {
if (biblatexBackend != null) {
bibChanged = !biblatexBackend.equals(oldBackend);
}
else {
bibChanged = oldBLMode == null;
}
}
else {
bibChanged = true;
}
}
else {
bibChanged = oldBLMode != null;
}
if (bibChanged) {
TexlipseProperties.setSessionProperty(project,
TexlipseProperties.SESSION_BIBTEX_RERUN,
new String("true"));
}
}
if (biblatexMode) {
TexlipseProperties.setSessionProperty(project,
TexlipseProperties.SESSION_BIBLATEXMODE_PROPERTY,
new Boolean(true));
}
else {
TexlipseProperties.setSessionProperty(project,
TexlipseProperties.SESSION_BIBLATEXMODE_PROPERTY,
null);
}
TexlipseProperties.setSessionProperty(project,
TexlipseProperties.SESSION_BIBLATEXBACKEND_PROPERTY,
biblatexBackend);
}
/**
* Updates the references and project data based on the data in the
* parsed document.
*
* @param monitor Progress monitor
*/
private void updateReferences(IProgressMonitor monitor) {
this.updateLabels(parser.getLabels());
this.updateCommands(parser.getCommands());
IProject project = getCurrentProject();
if (project == null) return;
IFile cFile = ((FileEditorInput) editor.getEditorInput()).getFile();
boolean isMainFile = cFile.equals(TexlipseProperties.getProjectSourceFile(project));
pollCancel(monitor);
// After here we just store those fun properties...
if (parser.isLocalBib()) {
TexlipseProperties.setSessionProperty(project,
TexlipseProperties.SESSION_BIBLATEXLOCALBIB_PROPERTY,
new Boolean(true));
}
else {
TexlipseProperties.setSessionProperty(project,
TexlipseProperties.SESSION_BIBLATEXLOCALBIB_PROPERTY,
null);
}
//Only update Preamble, Bibstyle if main Document
if (isMainFile) {
boolean biblatexMode = parser.isBiblatexMode();
updateBiblatex(project, biblatexMode, parser.getBiblatexBackend(), false);
String[] bibs = parser.getBibs();
this.updateBibs(bibs, biblatexMode, cFile);
pollCancel(monitor);
String preamble = parser.getPreamble();
if (preamble != null) {
TexlipseProperties.setSessionProperty(project,
TexlipseProperties.PREAMBLE_PROPERTY,
preamble);
}
if (!biblatexMode) {
String bibstyle = parser.getBibstyle();
if (bibstyle != null) {
String oldStyle = (String) TexlipseProperties.getSessionProperty(project,
TexlipseProperties.BIBSTYLE_PROPERTY);
if (oldStyle == null || !bibstyle.equals(oldStyle)) {
TexlipseProperties.setSessionProperty(project,
TexlipseProperties.BIBSTYLE_PROPERTY,
bibstyle);
// schedule running bibtex on the next build
TexlipseProperties.setSessionProperty(project,
TexlipseProperties.BIBFILES_CHANGED,
new Boolean(true));
}
}
}
}
}
/**
* Updates completions for the BibTeX -data
*
* @param bibNames Names of the BibTeX -files that the document uses
* @param resource The resource of the document
*/
private void updateBibs(String[] bibNames, boolean biblatexMode, IResource resource) {
IProject project = getCurrentProject();
if (project == null) return;
if (!biblatexMode) {
for (int i=0; i < bibNames.length; i++) {
if (!bibNames[i].endsWith(".bib")) {
bibNames[i] += ".bib";
}
}
}
if (bibContainer.checkFreshness(bibNames)) {
return;
}
TexlipseProperties.setSessionProperty(project,
TexlipseProperties.BIBFILE_PROPERTY,
bibNames);
List<String> newBibs = bibContainer.updateBibHash(bibNames);
IPath path = resource.getFullPath().removeFirstSegments(1).removeLastSegments(1);
if (!path.isEmpty())
path = path.addTrailingSeparator();
KpsewhichRunner filesearch = new KpsewhichRunner();
for (Iterator<String> iter = newBibs.iterator(); iter.hasNext();) {
String name = iter.next();
try {
String filepath = "";
//First try local search
IResource res = project.findMember(path + name);
//Try searching relative to main file
if (res == null) {
IContainer sourceDir = TexlipseProperties.getProjectSourceDir(project);
res = sourceDir.findMember(name);
}
if (res != null) {
filepath = res.getLocation().toOSString();
}
if (res == null) {
//Try Kpsewhich
filepath = filesearch.getFile(resource, name, "bibtex");
if (filepath.length() > 0 && !(new File(filepath).isAbsolute())) {
//filepath is a local path
res = project.findMember(path + filepath);
if (res != null) {
filepath = res.getLocation().toOSString();
}
else {
filepath = "";
}
}
else if (filepath.length() > 0) {
//Create a link to resource
IPath p = new Path(filepath);
if (name.indexOf('/') >= 0) {
//Remove path from name
name = name.substring(name.lastIndexOf('/') + 1);
}
IFile f = project.getFile(path + name);
if (f != null && !f.exists()) {
f.createLink(p, IResource.NONE, null);
}
}
}
if (filepath.length() > 0) {
BibParser parser = new BibParser(filepath);
try {
List<ReferenceEntry> bibEntriesList = parser.getEntries();
if (bibEntriesList != null && bibEntriesList.size() > 0) {
bibContainer.addRefSource(path + name, bibEntriesList);
} else if (bibEntriesList == null) {
MarkerHandler marker = MarkerHandler.getInstance();
marker.addFatalError(editor, "The BibTeX file " + filepath + " contains fatal errors, parsing aborted.");
continue;
}
} catch (IOException ioe) {
TexlipsePlugin.log("Can't read BibTeX file " + filepath, ioe);
}
} else {
MarkerHandler marker = MarkerHandler.getInstance();
marker.addFatalError(editor, "The BibTeX file " +name+ " not found.");
}
} catch (CoreException ce) {
TexlipsePlugin.log("Can't run Kpathsea", ce);
}
}
bibContainer.organize();
}
/**
* Updates the labels.
* @param labels
*/
private void updateLabels(List<ReferenceEntry> labels) {
IResource resource = getFile();
if (resource == null) return;
labelContainer.addRefSource(resource.getProjectRelativePath().toString(), labels);
labelContainer.organize();
}
/**
* Updates the commands.
* @param commands
*/
private void updateCommands(ArrayList<TexCommandEntry> commands) {
IResource resource = getFile();
if (resource == null) return;
if (commandContainer.addRefSource(resource.getProjectRelativePath().toString(), commands))
commandContainer.organize();
}
/**
* Checks whether all includes exists, if they are outside of the
* project, add a link to the file to the project
* @param includes
*/
private void processIncludes(List<OutlineNode> includes, IEditorInput input) {
IProject project = getCurrentProject();
if (project == null) return;
IFile referFile = (IFile) input.getAdapter(IFile.class);
if (referFile == null) return;
for (OutlineNode node : includes) {
IFile f = null;
IFile mainTexFile = TexlipseProperties.getProjectSourceFile(project);
if (mainTexFile != null) {
//Includes are always relative to the main file
f = TexProjectParser.findIFile(node.getName(), mainTexFile, project);
}
if (f == null) {
//Try finding it relative to refering file
f = TexProjectParser.findIFile(node.getName(), referFile, project);
}
if (f == null) {
MarkerHandler marker = MarkerHandler.getInstance();
String errorMsg = MessageFormat.format(
TexlipsePlugin.getResourceString("parseErrorIncludeNotFound"),
new Object[] { node.getName() });
marker.createErrorMarker(referFile, errorMsg, node.getBeginLine());
}
}
}
/**
* Creates the reference containers.
*
*/
private void createReferenceContainers() {
boolean parseAll = false;
IProject project = getCurrentProject();
if (project == null) {
if (bibContainer == null) bibContainer = new ReferenceContainer();
if (labelContainer == null) labelContainer = new ReferenceContainer();
if (commandContainer == null) commandContainer = new TexCommandContainer();
return;
}
ReferenceContainer bibCon = (ReferenceContainer) TexlipseProperties.getSessionProperty(project,
TexlipseProperties.BIBCONTAINER_PROPERTY);
if (bibCon == null) {
bibContainer = new ReferenceContainer();
TexlipseProperties.setSessionProperty(project,
TexlipseProperties.BIBCONTAINER_PROPERTY,
bibContainer);
parseAll = true;
} else {
bibContainer = bibCon;
}
ReferenceContainer labCon = (ReferenceContainer) TexlipseProperties.getSessionProperty(project,
TexlipseProperties.LABELCONTAINER_PROPERTY);
if (labCon == null) {
labelContainer = new ReferenceContainer();
TexlipseProperties.setSessionProperty(project,
TexlipseProperties.LABELCONTAINER_PROPERTY,
labelContainer);
parseAll = true;
} else {
labelContainer = labCon;
}
TexCommandContainer comCon = (TexCommandContainer) TexlipseProperties.getSessionProperty(project,
TexlipseProperties.COMCONTAINER_PROPERTY);
if (comCon == null) {
commandContainer = new TexCommandContainer();
TexlipseProperties.setSessionProperty(project,
TexlipseProperties.COMCONTAINER_PROPERTY,
commandContainer);
parseAll = true;
} else {
commandContainer = comCon;
}
if (parseAll) {
createProjectDatastructs(project);
}
}
/**
* Creates all the project data structures. These include the reference
* completions (BibTeX and label), command completions, the preamble,
* the BibTeX style.
*
* @param project The current project
*/
private void createProjectDatastructs(IProject project) {
//IResource resource = ((FileEditorInput)editor.getEditorInput()).getFile();
IResource[] files = TexlipseProperties.getAllProjectFiles(project);
if (files != null) {
IFile mainFile = TexlipseProperties.getProjectSourceFile(project);
for (int i = 0; i < files.length; i++) {
//IPath path = files[i].getFullPath();
String ext = files[i].getFileExtension();
// here are the file types we want to parse
if ("tex".equals(ext) || "ltx".equals(ext) || "sty".equals(ext)) {
try {
String input = TexlipseProperties.getFileContents(files[i]);
LatexRefExtractingParser lrep = new LatexRefExtractingParser();
lrep.parse(input);
if (lrep.isFatalErrors()) {
MarkerHandler marker = MarkerHandler.getInstance();
marker.addFatalError(editor, "The file " + files[i].getFullPath() + " contains fatal errors, parsing aborted.");
continue;
}
List<ReferenceEntry> labels = lrep.getLabels();
if (labels.size() > 0) {
labelContainer.addRefSource(files[i].getProjectRelativePath().toString(), labels);
}
List<TexCommandEntry> commands = lrep.getCommands();
if (commands.size() > 0) {
commandContainer.addRefSource(files[i].getProjectRelativePath().toString(), commands);
}
//Only update Preamble, Bibstyle if main Document
if (files[i].equals(mainFile)) {
String[] bibs = lrep.getBibs();
boolean biblatexMode = lrep.isBiblatexMode();
String biblatexBackend = lrep.getBiblatexBackend();
this.updateBiblatex(project, biblatexMode, biblatexBackend, true);
this.updateBibs(bibs, biblatexMode, files[i]);
String preamble = lrep.getPreamble();
if (preamble != null) {
TexlipseProperties.setSessionProperty(project,
TexlipseProperties.PREAMBLE_PROPERTY,
preamble);
}
String bibstyle = lrep.getBibstyle();
if (bibstyle != null)
TexlipseProperties.setSessionProperty(project,
TexlipseProperties.BIBSTYLE_PROPERTY,
bibstyle);
}
} catch (IOException ioe) {
TexlipsePlugin.log("Unable to open file " + files[i].getFullPath() + " for parsing", ioe);
}
}
}
// save time by doing this last
labelContainer.organize();
commandContainer.organize();
}
}
/**
* @return the current file or null if the input is no file
* (e.g. repository entry)
*/
public IFile getFile(){
if (editor.getEditorInput() instanceof IFileEditorInput) {
return ((IFileEditorInput) editor.getEditorInput()).getFile();
}
return null;
}
/**
* @return the current project or null if this file belongs to
* no project
*/
private IProject getCurrentProject() {
return editor.getProject();
}
/**
* Marks the current OutlineInput dirty.
* @param dirty true if OutlineInput is marked dirty
*/
private synchronized void setDirty(boolean dirty) {
this.isDirty = dirty;
}
/**
* Cancels a job by throwing OperationCanceledException.
*
* Used by inner class jobs of this class.
*
* @param monitor releated to the Job polling the cancel state
*/
private void pollCancel(IProgressMonitor monitor) {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
}
/**
* Write a message on the status line.
* @param msg the message.
*/
public void setStatusLineErrorMessage(String msg){
SubStatusLineManager slm =
(SubStatusLineManager) editor.getEditorSite().getActionBars().getStatusLineManager();
slm.setErrorMessage(msg);
slm.setVisible(true);
}
/**
* clean the status line
*
*/
public void removeStatusLineErrorMessage(){
SubStatusLineManager slm =
(SubStatusLineManager) editor.getEditorSite().getActionBars().getStatusLineManager();
//slm.setVisible(false);
slm.setErrorMessage(null);
}
}