| author | hbehrens | 2008-11-06 13:07:59 (EST) |
|---|---|---|
| committer | sefftinge | 2008-11-06 13:07:59 (EST) |
| commit | 53b613734084d59c6fcdb675309008ce5fb81b77 (patch) (side-by-side diff) | |
| tree | 275c3bbe4dfb27459f170dbe267a495b1b2308f6 | |
| parent | ac618afabefc9eb130febe427cf7b3387b9bc0f9 (diff) | |
| download | org.eclipse.xtext-53b613734084d59c6fcdb675309008ce5fb81b77.zip org.eclipse.xtext-53b613734084d59c6fcdb675309008ce5fb81b77.tar.gz org.eclipse.xtext-53b613734084d59c6fcdb675309008ce5fb81b77.tar.bz2 | |
* fix: partial pasing error #253885 (by patch from Sebastian)
* feature: trigger reparse before content assist (by patch from Sebastian)
8 files changed, 217 insertions, 64 deletions
diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/IXtextDocument.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/IXtextDocument.java index 3ba8eb0..a5f9c59 100644 --- a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/IXtextDocument.java +++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/IXtextDocument.java @@ -1,7 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2008 itemis AG (http://www.itemis.eu) and others. + * 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 org.eclipse.xtext.ui.core.editor.model; import org.eclipse.jface.text.IDocument; +/** + * @author Sebastian Zarnekow + */ public interface IXtextDocument extends IDocument { public <T> T readOnly(UnitOfWork<T> work); @@ -13,5 +24,9 @@ public interface IXtextDocument extends IDocument { public void addModelListener(IXtextModelListener listener); public void removeModelListener(IXtextModelListener listener); + + void addXtextDocumentContentObserver(IXtextDocumentContentObserver listener); + + void removeXtextDocumentContentObserver(IXtextDocumentContentObserver listener); } diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/IXtextDocumentContentObserver.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/IXtextDocumentContentObserver.java new file mode 100644 index 0000000..f7a10cf --- a/dev/null +++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/IXtextDocumentContentObserver.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2008 itemis AG (http://www.itemis.eu) and others. + * 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 org.eclipse.xtext.ui.core.editor.model; + + +import org.eclipse.jface.text.IDocumentListener; + +/** + * @author Sebastian Zarnekow - Initial contribution and API + */ +public interface IXtextDocumentContentObserver extends IDocumentListener { + + void performNecessaryUpdates(UnitOfWork.Processor processor); + +} diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/UnitOfWork.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/UnitOfWork.java index dc38c26..a40906c 100644 --- a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/UnitOfWork.java +++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/UnitOfWork.java @@ -10,6 +10,13 @@ package org.eclipse.xtext.ui.core.editor.model; import org.eclipse.xtext.resource.XtextResource; +/** + * @author Sebastian Zarnekow + */ public interface UnitOfWork<T> { T exec(XtextResource resource) throws Exception; + + interface Processor { + <T> T process(UnitOfWork<T> transaction); + } }
\ No newline at end of file diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/XtextDocument.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/XtextDocument.java index 49b84d0..94cb945 100644 --- a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/XtextDocument.java +++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/XtextDocument.java @@ -1,3 +1,11 @@ +/******************************************************************************* + * Copyright (c) 2008 itemis AG (http://www.itemis.eu) and others. + * 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 org.eclipse.xtext.ui.core.editor.model; import java.io.IOException; @@ -76,6 +84,7 @@ public class XtextDocument extends Document implements IXtextDocument { public <T> T readOnly(UnitOfWork<T> work) { readLock.lock(); try { + updateContentBeforeRead(); return work.exec(resource); } catch (RuntimeException e) { throw e; @@ -91,7 +100,7 @@ public class XtextDocument extends Document implements IXtextDocument { try { T exec = work.exec(resource); checkAndUpdateMarkers(resource); - notifyDocumentListeners(resource); + notifyModelListeners(resource); // TODO track modifications and serialize back to the text buffer return exec; } catch (RuntimeException e) { @@ -115,12 +124,50 @@ public class XtextDocument extends Document implements IXtextDocument { modelListeners.remove(listener); } - private void notifyDocumentListeners(XtextResource res) { + private void notifyModelListeners(XtextResource res) { Object[] listeners = modelListeners.getListeners(); for (int i = 0; i < listeners.length; i++) { ((IXtextModelListener) listeners[i]).modelChanged(res); } } + + private ListenerList xtextDocumentObservers = new ListenerList(ListenerList.IDENTITY); + + public void addXtextDocumentContentObserver(IXtextDocumentContentObserver observer) { + addDocumentListener(observer); + xtextDocumentObservers.add(observer); + } + + public void removeXtextDocumentContentObserver(IXtextDocumentContentObserver observer) { + xtextDocumentObservers.remove(observer); + removeDocumentListener(observer); + } + + private <T> void updateContentBeforeRead() { + Object[] listeners = xtextDocumentObservers.getListeners(); + UnitOfWork.Processor processor = new LockAwareProcessor(); + for (int i = 0; i < listeners.length; i++) { + ((IXtextDocumentContentObserver) listeners[i]).performNecessaryUpdates(processor); + } + } + + class LockAwareProcessor implements UnitOfWork.Processor{ + + public <T> T process(UnitOfWork<T> transaction) { + if (transaction!=null) { + readLock.unlock(); + writeLock.lock(); + try { + return modify(transaction); + } finally { + readLock.lock(); + writeLock.unlock(); + } + } else + return null; + } + + } private void checkAndUpdateMarkers(XtextResource res) { IFile file = getAdapter(IFile.class); diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/reconciler/XtextDocumentReconcileStrategy.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/reconciler/XtextDocumentReconcileStrategy.java index ba5b726..778af10 100644 --- a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/reconciler/XtextDocumentReconcileStrategy.java +++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/reconciler/XtextDocumentReconcileStrategy.java @@ -8,52 +8,23 @@ *******************************************************************************/ package org.eclipse.xtext.ui.core.editor.reconciler; -import org.apache.log4j.Logger; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.reconciler.DirtyRegion; import org.eclipse.jface.text.reconciler.IReconcilingStrategy; -import org.eclipse.xtext.resource.XtextResource; -import org.eclipse.xtext.ui.core.editor.model.UnitOfWork; import org.eclipse.xtext.ui.core.editor.model.XtextDocument; /** * @author Peter Friese - Initial contribution and API * @author Sven Efftinge + * @author Sebastian Zarnekow */ public class XtextDocumentReconcileStrategy implements IReconcilingStrategy { - private static final Logger log = Logger.getLogger(XtextDocumentReconcileStrategy.class); private XtextDocument document; public void reconcile(final IRegion region) { - - if (log.isDebugEnabled()) - log.debug("Preparing reconciliation."); - - document.modify(new UnitOfWork<Object>() { - public Object exec(XtextResource resource) throws Exception { - try { - if (!(region instanceof ReplaceRegion)) { - throw new IllegalArgumentException("Region to be reconciled must be a ReplaceRegion"); - } - ReplaceRegion replaceRegionToBeProcessed = (ReplaceRegion) region; - - if(log.isTraceEnabled()) - log.trace("Parsing replace region '" + replaceRegionToBeProcessed.getText() + "'."); - - resource.update(replaceRegionToBeProcessed.getOffset(), replaceRegionToBeProcessed.getLength(), - replaceRegionToBeProcessed.getText()); - } - catch (Throwable t) { - if (log.isDebugEnabled()) - log.debug("Partial parsing failed. Performing full reparse", t); - resource.reparse(document.get()); - } - return null; - } - }); - + document.modify(XtextReconcilerHelper.createReconcileJob(region, document)); } public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) { diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/reconciler/XtextReconciler.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/reconciler/XtextReconciler.java index f428184..c25860b 100644 --- a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/reconciler/XtextReconciler.java +++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/reconciler/XtextReconciler.java @@ -21,8 +21,10 @@ import org.eclipse.jface.text.reconciler.IReconciler; import org.eclipse.jface.text.reconciler.IReconcilingStrategy; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.ui.core.editor.model.IXtextDocument; +import org.eclipse.xtext.ui.core.editor.model.IXtextDocumentContentObserver; import org.eclipse.xtext.ui.core.editor.model.UnitOfWork; import org.eclipse.xtext.ui.core.editor.model.XtextDocumentUtil; +import org.eclipse.xtext.ui.core.editor.model.UnitOfWork.Processor; /** * Standard JFace Reconcilers, e.g. the MonoReconciler, convert an replace event @@ -49,7 +51,7 @@ public class XtextReconciler extends Job implements IReconciler { private int delay; private IReconcilingStrategy strategy; - class DocumentListener implements IDocumentListener { + class DocumentListener implements IDocumentListener, IXtextDocumentContentObserver { public void documentAboutToBeChanged(DocumentEvent event) { // do nothing so far @@ -58,6 +60,16 @@ public class XtextReconciler extends Job implements IReconciler { public void documentChanged(DocumentEvent event) { handleDocumentChanged(event); } + + public void performNecessaryUpdates(Processor processor) { + final IXtextDocument document = XtextDocumentUtil.get(textViewer); + if (document != null) { + final ReplaceRegion replaceRegionToBeProcessed = getAndResetReplaceRegion(); + if (replaceRegionToBeProcessed != null) { + processor.process(XtextReconcilerHelper.createReconcileJob(replaceRegionToBeProcessed, document)); + } + } + } } @@ -118,10 +130,10 @@ public class XtextReconciler extends Job implements IReconciler { */ private void handleInputDocumentChanged(IDocument oldInput, IDocument newInput) { if (oldInput != null) { - oldInput.removeDocumentListener(documentListener); + ((IXtextDocument)oldInput).removeXtextDocumentContentObserver(documentListener); } if (newInput != null) { - newInput.addDocumentListener(documentListener); + ((IXtextDocument) newInput).addXtextDocumentContentObserver(documentListener); final IXtextDocument document = XtextDocumentUtil.get(textViewer); strategy.setDocument(document); document.modify(new UnitOfWork<Object>() { @@ -165,26 +177,32 @@ public class XtextReconciler extends Job implements IReconciler { if (log.isDebugEnabled()) { log.debug("Preparing reconciliation."); } - - final IXtextDocument document = XtextDocumentUtil.get(textViewer); + IStatus result = null; + final IXtextDocument document = XtextDocumentUtil.get(textViewer); if (document != null) { - final ReplaceRegion replaceRegionToBeProcessed; - synchronized (pendingReplaceRegionLock) { - if (pendingReplaceRegion != null) { - replaceRegionToBeProcessed = pendingReplaceRegion; - } - else { - replaceRegionToBeProcessed = null; - } - pendingReplaceRegion = null; - } + final ReplaceRegion replaceRegionToBeProcessed = getAndResetReplaceRegion(); if (replaceRegionToBeProcessed != null) { strategy.reconcile(replaceRegionToBeProcessed); } } if (log.isDebugEnabled()) - log.debug("Reconciliation finished. Time required: " + (System.currentTimeMillis() - start)); + log.debug("Reconciliation finished. Time required: " + (System.currentTimeMillis() - start)); return (result != null) ? result : Status.OK_STATUS; } + + private ReplaceRegion getAndResetReplaceRegion() { + final ReplaceRegion replaceRegionToBeProcessed; + synchronized (pendingReplaceRegionLock) { + if (pendingReplaceRegion != null) { + replaceRegionToBeProcessed = pendingReplaceRegion; + } + else { + replaceRegionToBeProcessed = null; + } + pendingReplaceRegion = null; + } + return replaceRegionToBeProcessed; + } + } diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/reconciler/XtextReconcilerHelper.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/reconciler/XtextReconcilerHelper.java new file mode 100644 index 0000000..c8cddf6 --- a/dev/null +++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/reconciler/XtextReconcilerHelper.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2008 itemis AG (http://www.itemis.eu) and others. + * 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 org.eclipse.xtext.ui.core.editor.reconciler; + +import org.apache.log4j.Logger; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.ui.core.editor.model.UnitOfWork; + +/** + * @author Sebastian Zarnekow - Initial contribution and API + */ +public class XtextReconcilerHelper { + + private static final Logger log = Logger.getLogger(XtextReconcilerHelper.class); + + public static UnitOfWork<Object> createReconcileJob(final IRegion region, final IDocument document) { + + return new UnitOfWork<Object>() { + public Object exec(XtextResource resource) throws Exception { + if (log.isDebugEnabled()) + log.debug("Preparing reconciliation."); + try { + if (!(region instanceof ReplaceRegion)) { + throw new IllegalArgumentException("Region to be reconciled must be a ReplaceRegion"); + } + ReplaceRegion replaceRegionToBeProcessed = (ReplaceRegion) region; + + if(log.isTraceEnabled()) + log.trace("Parsing replace region '" + replaceRegionToBeProcessed.getText() + "'."); + + resource.update(replaceRegionToBeProcessed.getOffset(), replaceRegionToBeProcessed.getLength(), + replaceRegionToBeProcessed.getText()); + } + catch (Throwable t) { + if (log.isDebugEnabled()) + log.debug("Partial parsing failed. Performing full reparse", t); + resource.reparse(document.get()); + } + return null; + } + }; + } +} diff --git a/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/parser/PartialParserTest.java b/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/parser/PartialParserTest.java index 72d5956..0f671d6 100644 --- a/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/parser/PartialParserTest.java +++ b/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/parser/PartialParserTest.java @@ -20,6 +20,7 @@ import org.eclipse.xtext.parser.impl.PartialParsingUtil; import org.eclipse.xtext.parsetree.AbstractNode; import org.eclipse.xtext.parsetree.CompositeNode; import org.eclipse.xtext.parsetree.LeafNode; +import org.eclipse.xtext.parsetree.NodeUtil; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.testlanguages.LookaheadLanguageStandaloneSetup; import org.eclipse.xtext.testlanguages.ReferenceGrammarStandaloneSetup; @@ -43,16 +44,38 @@ public class PartialParserTest extends AbstractPartialParserTest { String model = "bar a foo bar c b d foo bar b c"; parseAndCompareAllSubstrings(model); } - + private void parseAndCompareAllSubstrings(String model) throws Exception { CompositeNode rootNode = getRootNode(model); - for (int i = 0; i < model.length()-1; ++i) { - for(int j=1; j+i<model.length(); ++j) { + for (int i = 0; i < model.length() - 1; ++i) { + for (int j = 1; j + i < model.length(); ++j) { partiallyParseAndCompare(rootNode, i, j); } } } - + + public void testErrorMarkers() throws Exception { + with(ReferenceGrammarStandaloneSetup.class); + String model = "spielplatz 1 {kind (k 1}"; // model contains an error + // due to missing ) at idx + // 23 + XtextResource resource = getResourceFromString(model); + assertEquals(1, resource.getParseResult().getParseErrors().size()); + CompositeNode rootNode = resource.getParseResult().getRootNode(); + AbstractNode leaf = NodeUtil.findLeafNodeAtOffset(rootNode, 24); + assertTrue(leaf.getSyntaxError() != null); + // resource.update(23, 0, ")"); + // assertTrue(resource.getParseResult().getParseErrors().isEmpty()); + IParseResult reparse = PartialParsingUtil.reparse(getParser(), rootNode, 23, 0, ")"); + rootNode = reparse.getRootNode(); + String expectedFixedModel = "spielplatz 1 {kind (k 1)}"; + String fixedModel = rootNode.serialize(); + assertEquals("serialized model as expected", expectedFixedModel, fixedModel); + resource = getResourceFromString(fixedModel); + assertEquals("full reparse is fine", 0, resource.getParseResult().getParseErrors().size()); + assertTrue("partial reparse is fine", reparse.getParseErrors() == null || reparse.getParseErrors().isEmpty()); + } + public void testGrammarElementAssigned() throws Exception { with(ReferenceGrammarStandaloneSetup.class); String model = "spielplatz 1 {kind (k 1)\n}"; @@ -63,7 +86,7 @@ public class PartialParserTest extends AbstractPartialParserTest { rootNode = reparse.getRootNode(); checkGrammarAssigned(rootNode); } - + @SuppressWarnings("serial") private void checkGrammarAssigned(CompositeNode rootNode) { TreeIterator<AbstractNode> iter = new AbstractTreeIterator<AbstractNode>(rootNode) { @@ -71,17 +94,17 @@ public class PartialParserTest extends AbstractPartialParserTest { protected Iterator<? extends AbstractNode> getChildren(Object object) { if (object instanceof CompositeNode) return ((CompositeNode) object).getChildren().iterator(); - return Collections.<AbstractNode>emptyList().iterator(); + return Collections.<AbstractNode> emptyList().iterator(); } }; - while(iter.hasNext()) { + while (iter.hasNext()) { AbstractNode node = iter.next(); assertNotNull(node.getGrammarElement()); EObject grammarElement = node.getGrammarElement(); - assertEquals(node.getParent()==null, grammarElement instanceof ParserRule); + assertEquals(node.getParent() == null, grammarElement instanceof ParserRule); } } - + @SuppressWarnings("unchecked") public void testNodeState() throws Exception { with(SimpleExpressionsStandaloneSetup.class); @@ -89,9 +112,9 @@ public class PartialParserTest extends AbstractPartialParserTest { CompositeNode rootNode = getRootNode(model); Iterator iter = rootNode.getLeafNodes().iterator(); boolean found = false; - while(iter.hasNext()) { + while (iter.hasNext()) { LeafNode leaf = (LeafNode) iter.next(); - if ( leaf.getText().equals("c")) { + if (leaf.getText().equals("c")) { assertEquals("before", 3, leaf.getLine()); assertEquals("before", 10, leaf.getOffset()); found = true; @@ -102,9 +125,9 @@ public class PartialParserTest extends AbstractPartialParserTest { assertTrue(reparse.getParseErrors() == null || reparse.getParseErrors().isEmpty()); iter = rootNode.getLeafNodes().iterator(); found = false; - while(iter.hasNext()) { + while (iter.hasNext()) { LeafNode leaf = (LeafNode) iter.next(); - if ( leaf.getText().equals("xy")) { + if (leaf.getText().equals("xy")) { assertEquals("after", 3, leaf.getLine()); assertEquals("after", 10, leaf.getOffset()); found = true; @@ -114,9 +137,11 @@ public class PartialParserTest extends AbstractPartialParserTest { } private void partiallyParseAndCompare(CompositeNode rootNode, int offset, int length) throws Exception { - PartialParsingPointers parsingPointers = PartialParsingUtil.calculatePartialParsingPointers(rootNode, offset, length); + PartialParsingPointers parsingPointers = PartialParsingUtil.calculatePartialParsingPointers(rootNode, offset, + length); String entryRuleName = parsingPointers.findEntryRuleName(); - IParseResult parseResult = ((AbstractParser) getParser()).parse(entryRuleName, new StringInputStream(parsingPointers.findReparseRegion()), getASTFactory()); + IParseResult parseResult = ((AbstractParser) getParser()).parse(entryRuleName, new StringInputStream( + parsingPointers.findReparseRegion()), getASTFactory()); comparator.assertSameStructure(parsingPointers.getDefaultReplaceRootNode(), parseResult.getRootNode()); comparator.assertSameStructure(parsingPointers.findASTReplaceElement(), parseResult.getRootASTElement()); assertEquals(parsingPointers.getDefaultReplaceRootNode().serialize(), parseResult.getRootNode().serialize()); |

