Add SourceViewer#computeStyleRanges() to highlight external documents#3868
Add SourceViewer#computeStyleRanges() to highlight external documents#3868tobiasmelcher wants to merge 4 commits intoeclipse-platform:masterfrom
Conversation
6571f85 to
18539e5
Compare
|
test error org.eclipse.e4.ui.tests.workbench.PartRenderingEngineTests#ensureCleanUpAddonCleansUp not related to this change (#3581) |
18539e5 to
0afb18c
Compare
This test is fixed now (since yesterday) |
vogella
left a comment
There was a problem hiding this comment.
Idea is sound and SourceViewer is a reasonable place for this. Main concern is exception-safety of the partitioner swap (see inline on the final connect(originalDocument)); the rest are tightenings.
| * The viewer's partitioner is temporarily connected to the given document so that its | ||
| * presentation repairers can compute the correct highlighting. The viewer's own document is not | ||
| * affected. | ||
| * </p> |
There was a problem hiding this comment.
The Javadoc says "The viewer's own document is not affected", but this method temporarily disconnects the original document's partitioner and reconnects it only on the happy path. Worth documenting the side effect on the original document's partitioner state, and the UI-thread-only assumption.
| * @throws BadLocationException if {@code damage} is outside the bounds of {@code document} | ||
| * @since 3.31 | ||
| */ | ||
| public List<StyleRange> computeStyleRanges(IDocument document, IRegion damage) throws BadLocationException { |
There was a problem hiding this comment.
Nit: damage leaks reconciler/repairer jargon into a public API. For callers who aren't thinking in damage/repair terms, region would read more naturally.
| } | ||
| IDocument originalDocument= getDocument(); | ||
| IDocumentPartitioner partitioner= originalDocument.getDocumentPartitioner(); | ||
| document.setDocumentPartitioner(partitioner); |
There was a problem hiding this comment.
This document.setDocumentPartitioner(partitioner) is redundant with the IDocumentExtension3 branch below, which sets a partitioner on document again via ext.setDocumentPartitioner(partition, ...). In the common case the partitioner is set twice. Consider picking one path.
| try { | ||
| originalDocumentPartitioner.connect(document); | ||
| } finally { | ||
| ext.setDocumentPartitioner(partition, originalDocumentPartitioner); | ||
| } |
There was a problem hiding this comment.
The finally here runs ext.setDocumentPartitioner(partition, originalDocumentPartitioner) on the external document even when connect(document) throws. That isn't a safety pattern — it's guaranteed cleanup pointed at the wrong object. If connect fails, the original document's partitioner is already disconnected and nothing restores it.
| if (originalDocumentPartitioner != null) { | ||
| originalDocumentPartitioner.connect(originalDocument); | ||
| } |
There was a problem hiding this comment.
Not exception-safe. If TextUtilities.computePartitioning or any repairer.createPresentation above throws, this connect(originalDocument) never runs and the viewer is left with its own document's partitioner connected to the external document. The whole swap-and-use block should be wrapped in try { ... } finally { if (originalDocumentPartitioner != null) originalDocumentPartitioner.connect(originalDocument); } (plus restoring any partitioner mapping that was changed on the original document).
Introduces computeStyleRanges(IDocument, IRegion) on SourceViewer, which applies the viewer's own presentation reconciler and partitioner to an external document, returning the resulting SWT StyleRanges for the given region without affecting the viewer's current document.
0afb18c to
c47d26a
Compare
|
I'm not a committer but I guess it might be useful to add some tests ensuring there won't be any regressions here? |
For API especially. |
@danthe1st @trancexpress test added with d6456a7 |
|
Thanks for the iteration @tobiasmelcher. Two remaining concerns before I'm comfortable approving. The unit-test angle can be handled in a follow-up.
|
SourceViewer#computeStyleRanges()
@vogella thanks a lot for the review - should be now done with 392609f |
| documentExt.setDocumentPartitioner(partition, originalDocumentPartitioner); | ||
| } else { | ||
| externalDocPartitioner= document.getDocumentPartitioner(); | ||
| document.setDocumentPartitioner(originalDocument.getDocumentPartitioner()); |
There was a problem hiding this comment.
This else branch is inconsistent with the IDocumentExtension3 path above: it swaps the partitioner reference on the external document via setDocumentPartitioner but never calls disconnect() / connect(document). The partitioner is still connected to originalDocument while being asked to partition document, which can produce wrong results or corrupt the partitioner's internal state. The matching finally branch has the same issue.
Since Document (the standard implementation) always implements IDocumentExtension3, one clean option is to drop the else branch entirely and require IDocumentExtension3 on both documents (throwing or returning an empty list otherwise). Alternatively, mirror the disconnect/connect dance from the happy path:
| document.setDocumentPartitioner(originalDocument.getDocumentPartitioner()); | |
| } else { | |
| externalDocPartitioner= document.getDocumentPartitioner(); | |
| IDocumentPartitioner origPartitioner= originalDocument.getDocumentPartitioner(); | |
| if (origPartitioner != null) { | |
| origPartitioner.disconnect(); | |
| origPartitioner.connect(document); | |
| } | |
| document.setDocumentPartitioner(origPartitioner); | |
| } |
The finally branch would need the symmetric disconnect/reconnect back to originalDocument. Also, please add a test that exercises a non-IDocumentExtension3 document so this path isn't untested.
| Assert.isTrue(Display.getCurrent() != null, "computeStyleRanges must be called from SWT UI thread"); //$NON-NLS-1$ | ||
| IDocument originalDocument= getDocument(); | ||
| Assert.isNotNull(originalDocument, "viewer must have a document before calling computeStyleRanges"); //$NON-NLS-1$ | ||
| String partition= IDocumentExtension3.DEFAULT_PARTITIONING; |
There was a problem hiding this comment.
Naming nit: this variable holds a partitioning name (a namespace like DEFAULT_PARTITIONING), not a partition/content-type. IPresentationReconcilerExtension#getDocumentPartitioning() and IDocumentExtension3#getDocumentPartitioner(String partitioning) use the term "partitioning". Renaming to partitioning (and extPartition → extPartitioning) would match surrounding API vocabulary and make the calls below read correctly.
| String partition= IDocumentExtension3.DEFAULT_PARTITIONING; | |
| String partitioning= IDocumentExtension3.DEFAULT_PARTITIONING; |
Requires the same rename at the uses further down (documentExt.getDocumentPartitioner(partition), setDocumentPartitioner(partition, ...), TextUtilities.computePartitioning(document, partition, ...)).
| externalDocPartitioner= document.getDocumentPartitioner(); | ||
| document.setDocumentPartitioner(originalDocument.getDocumentPartitioner()); | ||
| } | ||
| TextPresentation presentation= new TextPresentation(region, 1000); |
There was a problem hiding this comment.
Magic number: 1000 is an expected-size hint for TextPresentation. For small regions it over-allocates; for large regions it under-allocates. Derive it from the region length (or drop the hint and use the default constructor) so it scales with actual input.
| TextPresentation presentation= new TextPresentation(region, 1000); | |
| TextPresentation presentation= new TextPresentation(region, Math.max(region.getLength() / 10, 16)); |
There was a problem hiding this comment.
I got this magic value from PresentationReconciler.createPresentation. There is no constructor which allows to pass only a damage region. I will take over your proposal.
Introduces computeStyleRanges(IDocument, IRegion) on SourceViewer, which applies the viewer's own presentation reconciler and partitioner to an external document, returning the resulting SWT StyleRanges for the given region without affecting the viewer's current document.
This method will be used by the unified diff implementation to calculate the syntax coloring for the parts rendered inside the code mining which are not part of the original editor document (see eclipse-platform/eclipse.platform#2560).