package org.keplerproject.ldt.ui.editors.outline; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentListener; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.StructuredViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.texteditor.IDocumentProvider; import org.keplerproject.ldt.ui.editors.LuaEditor; //Do this for the whole class until a compliance level is committed to @SuppressWarnings("unchecked") public class LuaOutlineContentProvider implements ITreeContentProvider { public static String OFFSET_PROPERTY = "offsets"; StructuredViewer fViewer = null; LuaEditor fEditorInput = null; IDocument fDocument; IDocumentListener fDocumentListener; //Maintain a map of function name -> FunctionDefinition //Use this to do incremental updates of only the differences in editing HashMap fFunctionCache = new HashMap(); //TODO: Extract this to an external class public static final class FunctionDefinition { String fName; int fCharOffset; int fCharEndOffset; public FunctionDefinition(String name, int charOffset, int charEndOffset) { fName = name; fCharOffset = charOffset; fCharEndOffset = charEndOffset; } public String getName() { return fName; } public int getCharacterOffset() { return fCharOffset; } public void setCharacterOffset(int offset) { fCharOffset = offset; } public int getCharacterEndOffset() { return fCharEndOffset; } public void setCharacterEndOffset(int offset) { fCharEndOffset = offset; } public String toString() { return getName(); } public boolean offsetsMatch(FunctionDefinition def) { if(getCharacterOffset() != def.getCharacterOffset()) { return false; } if(getCharacterEndOffset() != def.getCharacterEndOffset()) { return false; } return true; } }; public LuaOutlineContentProvider() { fDocumentListener = new IDocumentListener() { public void documentAboutToBeChanged(DocumentEvent event) { } public void documentChanged(DocumentEvent event) { refreshCache(true); } }; } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { if(fEditorInput != null) { if(fDocument != null) { fDocument.removeDocumentListener(fDocumentListener); } } fViewer = null; fEditorInput = null; fDocument = null; fFunctionCache.clear(); if(newInput instanceof LuaEditor) { fViewer = (StructuredViewer)viewer; fEditorInput = (LuaEditor)newInput; IDocumentProvider provider = fEditorInput.getDocumentProvider(); fDocument = provider.getDocument(fEditorInput.getEditorInput()); if(fDocument != null) { fDocument.addDocumentListener(fDocumentListener); } refreshCache(false); } } public Object[] getElements(Object inputElement) { if(inputElement == fEditorInput && fDocument != null) { return fFunctionCache.values().toArray(); } return new Object[0]; } public Object[] getChildren(Object parentElement) { return getElements(parentElement); } public Object getParent(Object element) { return null; } public boolean hasChildren(Object element) { return getElements(element).length > 0; } public void dispose() { } protected void refreshCache(boolean doRefresh) { //TODO: Put in a background refresh thread backgroundRefresh(doRefresh); } protected void backgroundRefresh(boolean doRefresh) { if(fViewer == null) { fFunctionCache.clear(); return; } //Parse the document content and extract all function information FunctionDefinition [] allDefs; String content = fDocument.get(); if(content != null) { allDefs = parseFunctions(content); } else { allDefs = new FunctionDefinition[0]; } //Get a copy of the original keys to use as markers Set oldKeys = new HashSet(fFunctionCache.keySet()); final ArrayList updateList = new ArrayList(); //Correlate and update as required boolean functionAdded = false; for(int i = 0; i < allDefs.length; i++) { FunctionDefinition newDef = (FunctionDefinition)allDefs[i]; final FunctionDefinition oldDef = (FunctionDefinition)fFunctionCache.get(newDef.getName()); if(oldDef == null) { fFunctionCache.put(newDef.getName(), newDef); functionAdded = true; } else { oldKeys.remove(newDef.getName()); if(!oldDef.offsetsMatch(newDef)) { oldDef.setCharacterOffset(newDef.getCharacterOffset()); oldDef.setCharacterEndOffset(newDef.getCharacterEndOffset()); updateList.add(oldDef); } } } //Check and see if any items were deleted, that causes a full refresh boolean functionRemoved = oldKeys.size() != 0; Iterator it = oldKeys.iterator(); while(it.hasNext()) { FunctionDefinition oldDef = (FunctionDefinition)fFunctionCache.get(it.next()); fFunctionCache.remove(oldDef.getName()); } if(!doRefresh) { return; } final boolean fullRefresh = functionAdded || functionRemoved; Display display = fViewer.getControl().getDisplay(); display.syncExec(new Runnable() { public void run() { if(fViewer == null || fViewer.getControl().isDisposed()) { return; } if(fullRefresh) { fViewer.refresh(); } else { String [] offsetProperty = new String[] { OFFSET_PROPERTY }; FunctionDefinition [] elements; elements = (FunctionDefinition[])updateList.toArray(new FunctionDefinition[updateList.size()]); fViewer.update(elements, offsetProperty); } } }); } //This is kind of a weak identification, but it is fairly resilient in the face of errors Pattern fLuaFunctionPattern = Pattern.compile("^\\w*function\\s+(\\w+)\\s*\\(.*$", Pattern.MULTILINE); protected FunctionDefinition [] parseFunctions(String fileContents) { ArrayList<FunctionDefinition> functionList = new ArrayList<FunctionDefinition>(); Matcher matcher = fLuaFunctionPattern.matcher(fileContents); int offset = 0; while(matcher.find(offset)) { int startOffset = matcher.start(); offset = matcher.end(); String functionName = matcher.group(1); functionList.add(new FunctionDefinition(functionName, startOffset, offset)); } return (FunctionDefinition [])functionList.toArray(new FunctionDefinition[functionList.size()]); } }