Search in sources :

Example 16 with ITextViewer

use of org.eclipse.jface.text.ITextViewer in project webtools.sourceediting by eclipse.

the class CSSCompletionProposalComputer method computeCompletionProposals.

/**
 * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#computeCompletionProposals(org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext, org.eclipse.core.runtime.IProgressMonitor)
 */
public List computeCompletionProposals(CompletionProposalInvocationContext context, IProgressMonitor monitor) {
    ITextViewer viewer = context.getViewer();
    int documentPosition = context.getInvocationOffset();
    IndexedRegion indexedNode = ContentAssistUtils.getNodeAt(viewer, documentPosition);
    IDOMNode xNode = null;
    IDOMNode parent = null;
    CSSProposalArranger arranger = null;
    // If there is a selected region, we'll need to replace the text
    ITextSelection selection = (ITextSelection) viewer.getSelectionProvider().getSelection();
    // if(indexedNode == null) return new ICompletionProposal[0];
    if (indexedNode instanceof IDOMNode) {
        xNode = (IDOMNode) indexedNode;
        parent = (IDOMNode) xNode.getParentNode();
    }
    // case
    if ((xNode != null) && xNode.getNodeName().equalsIgnoreCase(HTML40Namespace.ElementName.STYLE)) {
        // now we know the cursor is in a <style> tag w/out region
        IStructuredModel cssModel = getCSSModel(xNode);
        if (cssModel != null) {
            // adjust offsets for embedded style
            int offset = documentPosition;
            int pos = 0;
            IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(pos);
            if (keyIndexedNode == null) {
                keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument();
            }
            arranger = new CSSProposalArranger(pos, (ICSSNode) keyIndexedNode, offset, selection.getLength(), (char) 0);
        }
    } else if ((parent != null) && parent.getNodeName().equalsIgnoreCase(HTML40Namespace.ElementName.STYLE)) {
        // now we know the cursor is in a <style> tag with a region
        // use the parent because that will be the <style> tag
        IStructuredModel cssModel = getCSSModel(parent);
        if (cssModel != null) {
            // adjust offsets for embedded style
            int offset = indexedNode.getStartOffset();
            int pos = documentPosition - offset;
            IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(pos);
            if (keyIndexedNode == null) {
                keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument();
            }
            arranger = new CSSProposalArranger(pos, (ICSSNode) keyIndexedNode, offset, selection.getLength(), (char) 0);
        }
    } else if (indexedNode instanceof IDOMNode) {
        IDOMNode domNode = ((IDOMNode) indexedNode);
        // get model for node w/ style attribute
        IStructuredModel cssModel = getCSSModel(domNode);
        if (cssModel != null) {
            // adjust offsets for embedded style
            int textRegionStartOffset = getTextRegionStartOffset(domNode, documentPosition);
            int pos = documentPosition - textRegionStartOffset;
            char quote = (char) 0;
            try {
                quote = context.getDocument().get(textRegionStartOffset, 1).charAt(0);
            } catch (BadLocationException e) {
                Logger.logException("error getting quote character", e);
            }
            // get css indexed region
            IndexedRegion cssIndexedNode = cssModel.getIndexedRegion(pos);
            if (cssIndexedNode == null) {
                cssIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument();
            }
            if (cssIndexedNode instanceof ICSSNode) {
                // inline style for a tag, not embedded
                arranger = new CSSProposalArranger(pos, (ICSSNode) cssIndexedNode, textRegionStartOffset, selection.getLength(), quote);
            }
        }
    } else if (indexedNode instanceof ICSSNode) {
        // when editing external CSS using CSS Designer, ICSSNode is passed.
        ICSSDocument cssdoc = ((ICSSNode) indexedNode).getOwnerDocument();
        if (cssdoc != null) {
            IStructuredModel cssModel = cssdoc.getModel();
            if (cssModel != null) {
                IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(documentPosition);
                if (keyIndexedNode == null) {
                    keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument();
                }
                if (keyIndexedNode instanceof ICSSNode) {
                    // inline style for a tag, not embedded
                    arranger = new CSSProposalArranger(documentPosition, (ICSSNode) keyIndexedNode, 0, selection.getLength(), (char) 0);
                }
            }
        }
    } else if ((indexedNode == null) && ContentAssistUtils.isViewerEmpty(viewer)) {
        // the top of empty CSS Document
        IStructuredModel cssModel = null;
        try {
            cssModel = StructuredModelManager.getModelManager().getExistingModelForRead(viewer.getDocument());
            if (cssModel instanceof ICSSModel) {
                IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(documentPosition);
                if (keyIndexedNode == null) {
                    keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument();
                }
                if (keyIndexedNode instanceof ICSSNode) {
                    // inline style for a tag, not embedded
                    arranger = new CSSProposalArranger(documentPosition, (ICSSNode) keyIndexedNode, 0, (char) 0);
                }
            }
        } finally {
            if (cssModel != null)
                cssModel.releaseFromRead();
        }
    }
    ICompletionProposal[] proposals = new ICompletionProposal[0];
    if (arranger != null) {
        proposals = arranger.getProposals();
        ICompletionProposal[] newfileproposals = new ICompletionProposal[0];
        ICompletionProposal[] anyproposals = new ICompletionProposal[0];
        // add end tag if parent is not closed
        ICompletionProposal endTag = XMLContentAssistUtilities.computeXMLEndTagProposal(viewer, documentPosition, indexedNode, HTML40Namespace.ElementName.STYLE, SharedXMLEditorPluginImageHelper.IMG_OBJ_TAG_GENERIC);
        // add the additional proposals
        int additionalLength = newfileproposals.length + anyproposals.length;
        additionalLength = (endTag != null) ? ++additionalLength : additionalLength;
        if (additionalLength > 0) {
            ICompletionProposal[] plusOnes = new ICompletionProposal[proposals.length + additionalLength];
            int appendPos = proposals.length;
            // add end tag proposal
            if (endTag != null) {
                System.arraycopy(proposals, 0, plusOnes, 1, proposals.length);
                plusOnes[0] = endTag;
                ++appendPos;
            } else {
                System.arraycopy(proposals, 0, plusOnes, 0, proposals.length);
            }
            // add items in newfileproposals
            for (int i = 0; i < newfileproposals.length; ++i) {
                plusOnes[appendPos + i] = newfileproposals[i];
            }
            // add items in anyproposals
            appendPos = appendPos + newfileproposals.length;
            for (int i = 0; i < anyproposals.length; ++i) {
                plusOnes[appendPos + i] = anyproposals[i];
            }
            proposals = plusOnes;
        }
    }
    return Arrays.asList(proposals);
}
Also used : ICSSModel(org.eclipse.wst.css.core.internal.provisional.document.ICSSModel) ICSSNode(org.eclipse.wst.css.core.internal.provisional.document.ICSSNode) ICSSDocument(org.eclipse.wst.css.core.internal.provisional.document.ICSSDocument) IndexedRegion(org.eclipse.wst.sse.core.internal.provisional.IndexedRegion) ITextSelection(org.eclipse.jface.text.ITextSelection) ITextViewer(org.eclipse.jface.text.ITextViewer) IDOMNode(org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode) ICompletionProposal(org.eclipse.jface.text.contentassist.ICompletionProposal) IStructuredModel(org.eclipse.wst.sse.core.internal.provisional.IStructuredModel) BadLocationException(org.eclipse.jface.text.BadLocationException)

Example 17 with ITextViewer

use of org.eclipse.jface.text.ITextViewer in project webtools.sourceediting by eclipse.

the class JSPJavaCompletionProposalComputer method isValidContext.

/**
 * <p>Determines if the context is a valid one for JSP Java proposals.
 * The default result is <code>true</code></p>
 *
 * @param context check this context to see if it is valid for JSP
 * Java proposals
 * @return <code>true</code> if the given context is a valid one for
 * JSP Java proposals, <code>false</code> otherwise.  <code>true</code>
 * is the default response if a specific case for <code>false</code> is
 * not found.
 */
private boolean isValidContext(CompletionProposalInvocationContext context) {
    ITextViewer viewer = context.getViewer();
    int documentPosition = context.getInvocationOffset();
    String partitionType = getPartitionType(viewer, documentPosition);
    if (partitionType == IJSPPartitions.JSP_CONTENT_JAVA)
        return true;
    IStructuredDocument structuredDocument = (IStructuredDocument) viewer.getDocument();
    IStructuredDocumentRegion fn = structuredDocument.getRegionAtCharacterOffset(documentPosition);
    IStructuredDocumentRegion sdRegion = ContentAssistUtils.getStructuredDocumentRegion(viewer, documentPosition);
    // check for xml-jsp tags...
    if (partitionType == IJSPPartitions.JSP_DIRECTIVE && fn != null) {
        IStructuredDocumentRegion possibleXMLJSP = ((fn.getType() == DOMRegionContext.XML_CONTENT) && fn.getPrevious() != null) ? fn.getPrevious() : fn;
        ITextRegionList regions = possibleXMLJSP.getRegions();
        if (regions.size() > 1) {
            // check bounds cases
            ITextRegion xmlOpenOrClose = regions.get(0);
            if (xmlOpenOrClose.getType() != DOMRegionContext.XML_TAG_OPEN && documentPosition != possibleXMLJSP.getStartOffset() && xmlOpenOrClose.getType() != DOMRegionContext.XML_END_TAG_OPEN && documentPosition <= possibleXMLJSP.getStartOffset()) {
                // possible xml-jsp
                ITextRegion nameRegion = regions.get(1);
                String name = possibleXMLJSP.getText(nameRegion);
                if (name.equals("jsp:scriptlet") || name.equals("jsp:expression") || name.equals("jsp:declaration")) {
                    // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                    return true;
                }
            }
        }
    }
    // check for XML-JSP in a <script> region
    if (partitionType == IJSPPartitions.JSP_CONTENT_JAVASCRIPT || partitionType == IHTMLPartitions.SCRIPT) {
        // fn should be block text
        IStructuredDocumentRegion decodedSDRegion = decodeScriptBlock(fn.getFullText());
        // decodedSDRegion.getEndOffset()));
        if (decodedSDRegion != null) {
            IStructuredDocumentRegion sdr = decodedSDRegion;
            while (sdr != null) {
                // sdr.getEndOffset()));
                if (sdr.getType() == DOMJSPRegionContexts.JSP_CONTENT) {
                    if (documentPosition >= fn.getStartOffset() + sdr.getStartOffset() && documentPosition <= fn.getStartOffset() + sdr.getEndOffset()) {
                        return true;
                    }
                } else if (sdr.getType() == DOMRegionContext.XML_TAG_NAME) {
                    if (documentPosition > fn.getStartOffset() + sdr.getStartOffset() && documentPosition < fn.getStartOffset() + sdr.getEndOffset()) {
                        return false;
                    } else if (documentPosition == fn.getStartOffset() + sdr.getEndOffset() && sdr.getNext() != null && sdr.getNext().getType() == DOMJSPRegionContexts.JSP_CONTENT) {
                        // <jsp:scriptlet>| blah </jsp:scriptlet>
                        return true;
                    } else if (documentPosition == fn.getStartOffset() + sdr.getStartOffset() && sdr.getPrevious() != null && sdr.getPrevious().getType() == DOMRegionContext.XML_TAG_NAME) {
                        return true;
                    }
                }
                sdr = sdr.getNext();
            }
        }
    }
    // check special JSP delimiter cases
    if (fn != null && partitionType == IJSPPartitions.JSP_CONTENT_DELIMITER) {
        IStructuredDocumentRegion fnDelim = fn;
        // if it's a nested JSP region, need to get the correct
        // StructuredDocumentRegion
        // not sure why this check was there...
        // if (fnDelim.getType() == XMLRegionContext.BLOCK_TEXT) {
        Iterator blockRegions = fnDelim.getRegions().iterator();
        ITextRegion temp = null;
        ITextRegionContainer trc;
        while (blockRegions.hasNext()) {
            temp = (ITextRegion) blockRegions.next();
            // we hit a nested
            if (temp instanceof ITextRegionContainer) {
                trc = (ITextRegionContainer) temp;
                // it's in this region
                if (documentPosition >= trc.getStartOffset() && documentPosition < trc.getEndOffset()) {
                    Iterator nestedJSPRegions = trc.getRegions().iterator();
                    while (nestedJSPRegions.hasNext()) {
                        temp = (ITextRegion) nestedJSPRegions.next();
                        if (XMLContentAssistUtilities.isJSPOpenDelimiter(temp.getType()) && documentPosition == trc.getStartOffset(temp)) {
                            // adapter
                            if (documentPosition > 0) {
                                partitionType = getPartitionType(viewer, documentPosition - 1);
                                break;
                            }
                        } else if (XMLContentAssistUtilities.isJSPCloseDelimiter(temp.getType()) && documentPosition == trc.getStartOffset(temp)) {
                            // JSP content assist
                            return true;
                        }
                    }
                }
            }
        // }
        }
        // take care of XML-JSP delimter cases
        if (XMLContentAssistUtilities.isXMLJSPDelimiter(fnDelim)) {
            // since it's a delimiter, we know it's a ITextRegionContainer
            ITextRegion firstRegion = fnDelim.getRegions().get(0);
            if (fnDelim.getStartOffset() == documentPosition && (firstRegion.getType() == DOMRegionContext.XML_TAG_OPEN)) {
            // |<jsp:scriptlet> </jsp:scriptlet>
            // (pa) commented out so that we get regular behavior JSP
            // macros etc...
            // return getHTMLCompletionProposals(viewer,
            // documentPosition);
            } else if (fnDelim.getStartOffset() == documentPosition && (firstRegion.getType() == DOMRegionContext.XML_END_TAG_OPEN)) {
                // adapter get the proposals
                if (documentPosition > 0) {
                    String checkType = getPartitionType(viewer, documentPosition - 1);
                    if (checkType != IJSPPartitions.JSP_CONTENT_JAVASCRIPT) {
                        // check is failing for XML-JSP (region is not javascript...)
                        return true;
                    }
                    partitionType = IJSPPartitions.JSP_CONTENT_JAVASCRIPT;
                }
            } else if ((firstRegion.getType() == DOMRegionContext.XML_TAG_OPEN) && documentPosition >= fnDelim.getEndOffset()) {
                // anything else inbetween
                return true;
            }
        } else if (XMLContentAssistUtilities.isJSPDelimiter(fnDelim)) {
            // the delimiter <%, <%=, <%!, ...
            if (XMLContentAssistUtilities.isJSPCloseDelimiter(fnDelim)) {
                if (documentPosition == fnDelim.getStartOffset()) {
                    // JAVASCRIPT adapter get the proposals
                    if (documentPosition > 0) {
                        String checkType = getPartitionType(viewer, documentPosition - 1);
                        if (checkType != IJSPPartitions.JSP_CONTENT_JAVASCRIPT) {
                            return true;
                        }
                        partitionType = IJSPPartitions.JSP_CONTENT_JAVASCRIPT;
                    }
                }
            } else if (XMLContentAssistUtilities.isJSPOpenDelimiter(fnDelim)) {
                // use embedded HTML results
                if (documentPosition == fnDelim.getEndOffset()) {
                    // it's at the EOF <%|
                    return true;
                }
            }
        }
    }
    // <!-- <% |%> -->
    if (fn != null && (fn.getType() == DOMRegionContext.XML_CDATA_TEXT || fn.getType() == DOMRegionContext.XML_COMMENT_TEXT)) {
        if (fn instanceof ITextRegionContainer) {
            Object[] cdataRegions = fn.getRegions().toArray();
            ITextRegion r = null;
            ITextRegion jspRegion = null;
            for (int i = 0; i < cdataRegions.length; i++) {
                r = (ITextRegion) cdataRegions[i];
                if (r instanceof ITextRegionContainer) {
                    // CDATA embedded container, or comment container
                    Object[] jspRegions = ((ITextRegionContainer) r).getRegions().toArray();
                    for (int j = 0; j < jspRegions.length; j++) {
                        jspRegion = (ITextRegion) jspRegions[j];
                        if (jspRegion.getType() == DOMJSPRegionContexts.JSP_CLOSE) {
                            if (sdRegion.getStartOffset(jspRegion) == documentPosition) {
                                return true;
                            }
                        }
                    }
                }
            }
        }
    }
    // check if it's in an attribute value, if so, don't add CDATA
    // proposal
    ITextRegion attrContainer = (fn != null) ? fn.getRegionAtCharacterOffset(documentPosition) : null;
    if (attrContainer != null && attrContainer instanceof ITextRegionContainer) {
        if (attrContainer.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) {
            // test location of the cursor
            // return null if it's in the middle of an open/close delimiter
            Iterator attrRegions = ((ITextRegionContainer) attrContainer).getRegions().iterator();
            ITextRegion testRegion = null;
            while (attrRegions.hasNext()) {
                testRegion = (ITextRegion) attrRegions.next();
                // need to check for other valid attribute regions
                if (XMLContentAssistUtilities.isJSPOpenDelimiter(testRegion.getType())) {
                    if (!(((ITextRegionContainer) attrContainer).getEndOffset(testRegion) <= documentPosition))
                        return false;
                } else if (XMLContentAssistUtilities.isJSPCloseDelimiter(testRegion.getType())) {
                    if (!(((ITextRegionContainer) attrContainer).getStartOffset(testRegion) >= documentPosition))
                        return false;
                }
            }
            // TODO: handle non-Java code such as nested tags
            if (testRegion.getType().equals(DOMJSPRegionContexts.JSP_CONTENT)) {
                return true;
            }
            return false;
        }
    }
    return true;
}
Also used : ITextRegionList(org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList) IStructuredDocumentRegion(org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion) ITextRegion(org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion) Iterator(java.util.Iterator) IStructuredDocument(org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument) ITextRegionContainer(org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionContainer) ITextViewer(org.eclipse.jface.text.ITextViewer)

Example 18 with ITextViewer

use of org.eclipse.jface.text.ITextViewer in project webtools.sourceediting by eclipse.

the class JSPJavaCompletionProposalComputer method computeContextInformation.

/**
 * @see org.eclipse.wst.xml.ui.internal.contentassist.AbstractXMLCompletionProposalComputer#computeContextInformation(org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext, org.eclipse.core.runtime.IProgressMonitor)
 */
public List computeContextInformation(CompletionProposalInvocationContext context, IProgressMonitor monitor) {
    ITextViewer viewer = context.getViewer();
    int documentOffset = context.getInvocationOffset();
    List results = new ArrayList();
    // need to compute context info here, if it's JSP, call java computer
    IDocument doc = viewer.getDocument();
    IDocumentPartitioner dp = null;
    if (doc instanceof IDocumentExtension3) {
        dp = ((IDocumentExtension3) doc).getDocumentPartitioner(IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING);
    }
    if (dp != null) {
        // IDocumentPartitioner dp = viewer.getDocument().getDocumentPartitioner();
        String type = dp.getPartition(documentOffset).getType();
        if (type == IJSPPartitions.JSP_DEFAULT || type == IJSPPartitions.JSP_CONTENT_JAVA) {
            // get context info from completion results...
            List proposals = computeCompletionProposals(context, monitor);
            for (int i = 0; i < proposals.size(); i++) {
                IContextInformation ci = ((ICompletionProposal) proposals.get(i)).getContextInformation();
                if (ci != null)
                    results.add(ci);
            }
        }
    }
    return results;
}
Also used : IDocumentExtension3(org.eclipse.jface.text.IDocumentExtension3) IDocumentPartitioner(org.eclipse.jface.text.IDocumentPartitioner) ICompletionProposal(org.eclipse.jface.text.contentassist.ICompletionProposal) ArrayList(java.util.ArrayList) IContextInformation(org.eclipse.jface.text.contentassist.IContextInformation) ITextRegionList(org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList) ArrayList(java.util.ArrayList) List(java.util.List) IDocument(org.eclipse.jface.text.IDocument) ITextViewer(org.eclipse.jface.text.ITextViewer)

Example 19 with ITextViewer

use of org.eclipse.jface.text.ITextViewer in project webtools.sourceediting by eclipse.

the class ActionTestView method createPartControl.

/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.IWorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
	 */
public void createPartControl(Composite parent) {
    ITextViewer text = new TextViewer(parent, SWT.READ_ONLY);
    text.setDocument(new Document());
    fControl = text.getTextWidget();
    text.getDocument().set("Use either the toolbar or the menu to run your actions\n\n");
}
Also used : Document(org.eclipse.jface.text.Document) IDocument(org.eclipse.jface.text.IDocument) ITextViewer(org.eclipse.jface.text.ITextViewer) ITextViewer(org.eclipse.jface.text.ITextViewer) TextViewer(org.eclipse.jface.text.TextViewer)

Example 20 with ITextViewer

use of org.eclipse.jface.text.ITextViewer in project webtools.sourceediting by eclipse.

the class OpenFileHyperlinkTracker method documentChanged.

/*
	 * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
	 */
public void documentChanged(DocumentEvent event) {
    if (fRememberedPosition != null) {
        if (!fRememberedPosition.isDeleted()) {
            event.getDocument().removePosition(fRememberedPosition);
            fActiveRegion = new Region(fRememberedPosition.getOffset(), fRememberedPosition.getLength());
            fRememberedPosition = null;
            ITextViewer viewer = getTextViewer();
            if (viewer != null) {
                StyledText widget = viewer.getTextWidget();
                if (widget != null && !widget.isDisposed()) {
                    widget.getDisplay().asyncExec(new Runnable() {

                        public void run() {
                            deactivate();
                        }
                    });
                }
            }
        } else {
            fActiveRegion = null;
            fRememberedPosition = null;
            deactivate();
        }
    }
}
Also used : StyledText(org.eclipse.swt.custom.StyledText) Region(org.eclipse.jface.text.Region) IRegion(org.eclipse.jface.text.IRegion) ITextViewer(org.eclipse.jface.text.ITextViewer)

Aggregations

ITextViewer (org.eclipse.jface.text.ITextViewer)75 BadLocationException (org.eclipse.jface.text.BadLocationException)13 IDocument (org.eclipse.jface.text.IDocument)13 Region (org.eclipse.jface.text.Region)13 StyledText (org.eclipse.swt.custom.StyledText)13 IRegion (org.eclipse.jface.text.IRegion)11 ArrayList (java.util.ArrayList)9 Point (org.eclipse.swt.graphics.Point)9 Test (org.junit.Test)9 ITextSelection (org.eclipse.jface.text.ITextSelection)8 IEditorPart (org.eclipse.ui.IEditorPart)8 ITextViewerExtension5 (org.eclipse.jface.text.ITextViewerExtension5)6 IStructuredDocumentRegion (org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion)6 Document (org.eclipse.jface.text.Document)5 IHyperlink (org.eclipse.jface.text.hyperlink.IHyperlink)5 ISourceViewer (org.eclipse.jface.text.source.ISourceViewer)5 Display (org.eclipse.swt.widgets.Display)5 ITextRegion (org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion)5 List (java.util.List)4 ITextViewerExtension (org.eclipse.jface.text.ITextViewerExtension)4