/******************************************************************************* * Copyright (c) 2007 IBM Corporation. * 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 * * Contributors: * Robert Fuhrer (rfuhrer@watson.ibm.com) - initial API and implementation *******************************************************************************/ package org.eclipse.imp.editor; import java.util.List; import org.eclipse.imp.editor.UniversalEditor.StructuredSourceViewerConfiguration; import org.eclipse.imp.parser.IParseController; import org.eclipse.imp.runtime.RuntimePlugin; import org.eclipse.imp.services.IAutoEditStrategy; import org.eclipse.imp.services.ILanguageSyntaxProperties; import org.eclipse.imp.services.base.DefaultAutoIndentStrategy; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentCommand; import org.eclipse.jface.text.DocumentRewriteSession; import org.eclipse.jface.text.DocumentRewriteSessionType; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension4; import org.eclipse.jface.text.formatter.IContentFormatter; import org.eclipse.jface.text.information.IInformationPresenter; import org.eclipse.jface.text.source.IOverviewRuler; import org.eclipse.jface.text.source.IVerticalRuler; import org.eclipse.jface.text.source.SourceViewerConfiguration; import org.eclipse.jface.text.source.projection.ProjectionViewer; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; public class StructuredSourceViewer extends ProjectionViewer { /** * Text operation code for requesting the outline for the current input. */ public static final int SHOW_OUTLINE= 51; /** * Text operation code for requesting the outline for the element at the current position. */ public static final int OPEN_STRUCTURE= 52; /** * Text operation code for requesting the hierarchy for the current input. */ public static final int SHOW_HIERARCHY= 53; /** * Text operation code for toggling the commenting of a selected range of text, or the current line. */ public static final int TOGGLE_COMMENT= 54; /** * Text operation code for toggling the display of "occurrences" of the * current selection, whatever that means to the current language. */ public static final int MARK_OCCURRENCES= 55; /** * Text operation code for indenting the currently selected text. */ public static final int INDENT_SELECTION= 60; private IInformationPresenter fOutlinePresenter; private IInformationPresenter fStructurePresenter; private IInformationPresenter fHierarchyPresenter; private IAutoEditStrategy fAutoEditStrategy; private IParseController fParseController; public StructuredSourceViewer(Composite parent, IVerticalRuler verticalRuler, IOverviewRuler overviewRuler, boolean showAnnotationsOverview, int styles) { super(parent, verticalRuler, overviewRuler, showAnnotationsOverview, styles); } /* * @see ITextOperationTarget#canDoOperation(int) */ public boolean canDoOperation(int operation) { switch(operation) { case SHOW_OUTLINE: return fOutlinePresenter != null; case OPEN_STRUCTURE: return fStructurePresenter != null; case SHOW_HIERARCHY: return fHierarchyPresenter != null; case TOGGLE_COMMENT: return true; case INDENT_SELECTION: return fAutoEditStrategy != null; } return super.canDoOperation(operation); } /* * @see ITextOperationTarget#doOperation(int) */ public void doOperation(int operation) { if (getTextWidget() == null) return; switch (operation) { case SHOW_OUTLINE: if (fOutlinePresenter != null) fOutlinePresenter.showInformation(); return; case OPEN_STRUCTURE: if (fStructurePresenter != null) fStructurePresenter.showInformation(); return; case SHOW_HIERARCHY: if (fHierarchyPresenter != null) fHierarchyPresenter.showInformation(); return; case TOGGLE_COMMENT: doToggleComment(); return; // mmk 4/8/08 case INDENT_SELECTION: doIndentLines(); return; } super.doOperation(operation); } public void setParseController(IParseController parseController) { fParseController = parseController; } public void setFormatter(IContentFormatter formatter) { fContentFormatter= formatter; } private void doToggleComment() { ILanguageSyntaxProperties syntaxProps= fParseController.getSyntaxProperties(); if (syntaxProps == null) return; IDocument doc= this.getDocument(); DocumentRewriteSession rewriteSession= null; Point p= this.getSelectedRange(); final String lineCommentPrefix= syntaxProps.getSingleLineCommentPrefix(); if (doc instanceof IDocumentExtension4) { IDocumentExtension4 extension= (IDocumentExtension4) doc; rewriteSession= extension.startRewriteSession(DocumentRewriteSessionType.SEQUENTIAL); } try { final int selStart= p.x; final int selLen= p.y; final int selEnd= selStart + selLen; final int startLine= doc.getLineOfOffset(selStart); int endLine= doc.getLineOfOffset(selEnd); if (selLen > 0 && lookingAtLineEnd(doc, selEnd)) endLine--; boolean linesAllHaveCommentPrefix= linesHaveCommentPrefix(doc, lineCommentPrefix, startLine, endLine); boolean useCommonLeadingSpace= true; // take from a preference? int leadingSpaceToUse= useCommonLeadingSpace ? calculateLeadingSpace(doc, startLine, endLine) : 0; for(int line= startLine; line <= endLine; line++) { int lineStart= doc.getLineOffset(line); int lineEnd= lineStart + doc.getLineLength(line) - 1; if (linesAllHaveCommentPrefix) { // remove the comment prefix from each line, wherever it occurs in the line int offset= lineStart; while (Character.isWhitespace(doc.getChar(offset)) && offset < lineEnd) { offset++; } // The first non-whitespace characters *must* be the single-line comment prefix doc.replace(offset, lineCommentPrefix.length(), ""); } else { // add the comment prefix to each line, after however many spaces leadingSpaceToAdd indicates int offset= lineStart + leadingSpaceToUse; doc.replace(offset, 0, lineCommentPrefix + " "); } } } catch (BadLocationException e) { e.printStackTrace(); } finally { if (doc instanceof IDocumentExtension4) { IDocumentExtension4 extension= (IDocumentExtension4) doc; extension.stopRewriteSession(rewriteSession); } restoreSelection(); } } private int calculateLeadingSpace(IDocument doc, int startLine, int endLine) { try { int result= Integer.MAX_VALUE; for(int line= startLine; line <= endLine; line++) { int lineStart= doc.getLineOffset(line); int lineEnd= lineStart + doc.getLineLength(line) - 1; int offset= lineStart; while (Character.isWhitespace(doc.getChar(offset)) && offset < lineEnd) { offset++; } int leadingSpaces= offset - lineStart; result= Math.min(result, leadingSpaces); } return result; } catch (BadLocationException e) { return 0; } } /** * @return true, if the given inclusive range of lines all start with the single-line comment prefix, * even if they have different amounts of leading whitespace */ private boolean linesHaveCommentPrefix(IDocument doc, String lineCommentPrefix, int startLine, int endLine) { try { int docLen= doc.getLength(); for(int line= startLine; line <= endLine; line++) { int lineStart= doc.getLineOffset(line); int lineEnd= lineStart + doc.getLineLength(line) - 1; int offset= lineStart; while (Character.isWhitespace(doc.getChar(offset)) && offset < lineEnd) { offset++; } if (docLen - offset > lineCommentPrefix.length() && doc.get(offset, lineCommentPrefix.length()).equals(lineCommentPrefix)) { // this line starts with the single-line comment prefix } else { return false; } } } catch (BadLocationException e) { return false; } return true; } private void doIndentLines() { IDocument doc= this.getDocument(); DocumentRewriteSession rewriteSession= null; Point p= this.getSelectedRange(); if (doc instanceof IDocumentExtension4) { IDocumentExtension4 extension= (IDocumentExtension4) doc; rewriteSession= extension.startRewriteSession(DocumentRewriteSessionType.SEQUENTIAL); } try { final int selStart= p.x; final int selLen= p.y; final int selEnd= selStart + selLen; final int startLine= doc.getLineOfOffset(selStart); int endLine= doc.getLineOfOffset(selEnd); if (selLen > 0 && lookingAtLineEnd(doc, selEnd)) endLine--; for(int line= startLine; line <= endLine; line++) { int lineStartOffset= doc.getLineOffset(line); // Replace the existing indentation with the desired indentation. // Use the language-specific AutoEditStrategy, which requires a DocumentCommand. DocumentCommand cmd= new DocumentCommand() { }; cmd.offset= lineStartOffset; cmd.length= 0; cmd.text= Character.toString('\t'); cmd.doit= true; cmd.shiftsCaret= false; fAutoEditStrategy.customizeDocumentCommand(doc, cmd); doc.replace(cmd.offset, cmd.length, cmd.text); } } catch (BadLocationException e) { RuntimePlugin.getInstance().logException("Indent Selection command failed", e); } finally { if (doc instanceof IDocumentExtension4) { IDocumentExtension4 extension= (IDocumentExtension4) doc; extension.stopRewriteSession(rewriteSession); } restoreSelection(); } } private boolean lookingAtLineEnd(IDocument doc, int pos) { String[] legalLineTerms= doc.getLegalLineDelimiters(); try { for(String lineTerm: legalLineTerms) { int len= lineTerm.length(); if (pos > len && doc.get(pos - len, len).equals(lineTerm)) { return true; } } } catch (BadLocationException e) { RuntimePlugin.getInstance().logException("Error examining document for line termination", e); } return false; } /* * @see ISourceViewer#configure(SourceViewerConfiguration) */ public void configure(SourceViewerConfiguration configuration) { /* * Prevent access to colors disposed in unconfigure(), see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=53641 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=86177 */ StyledText textWidget= getTextWidget(); if (textWidget != null && !textWidget.isDisposed()) { Color foregroundColor= textWidget.getForeground(); if (foregroundColor != null && foregroundColor.isDisposed()) textWidget.setForeground(null); Color backgroundColor= textWidget.getBackground(); if (backgroundColor != null && backgroundColor.isDisposed()) textWidget.setBackground(null); } super.configure(configuration); if (configuration instanceof StructuredSourceViewerConfiguration) { StructuredSourceViewerConfiguration sSVConfiguration= (StructuredSourceViewerConfiguration) configuration; fOutlinePresenter= sSVConfiguration.getOutlinePresenter(this); if (fOutlinePresenter != null) fOutlinePresenter.install(this); fStructurePresenter= sSVConfiguration.getOutlinePresenter(this); if (fStructurePresenter != null) fStructurePresenter.install(this); fHierarchyPresenter= sSVConfiguration.getHierarchyPresenter(this, true); if (fHierarchyPresenter != null) fHierarchyPresenter.install(this); if (fAutoIndentStrategies != null) { List<org.eclipse.jface.text.IAutoEditStrategy> strategies= (List<org.eclipse.jface.text.IAutoEditStrategy>) fAutoIndentStrategies.get(IDocument.DEFAULT_CONTENT_TYPE); // TODO If there are multiple IAudoEditStrategy's, we may pick up one that doesn't do indent. How to identify the right one? // SMS 5 Aug 2008: There's another problem here, in that the available strategy here // may not be of type IAutoEditStrategy. See bug #243212. To provide at least a // short term fix, I'm going to substitute an appropriate value when that turns out // to be the case. This may be revised if we decide to somehow avoid the possibility // that a strategy of an inappropriate type might appear here. if (strategies != null && strategies.size() > 0) { // fAutoEditStrategy= (IAutoEditStrategy) strategies.get(0); if (strategies.get(0) instanceof IAutoEditStrategy) fAutoEditStrategy= (IAutoEditStrategy) strategies.get(0); else fAutoEditStrategy = new DefaultAutoIndentStrategy(); } } } // if (fPreferenceStore != null) { // fPreferenceStore.addPropertyChangeListener(this); // initializeViewerColors(); // } } /* * @see org.eclipse.jface.text.source.ISourceViewerExtension2#unconfigure() * @since 3.0 */ public void unconfigure() { if (fOutlinePresenter != null) { fOutlinePresenter.uninstall(); fOutlinePresenter= null; } if (fStructurePresenter != null) { fStructurePresenter.uninstall(); fStructurePresenter= null; } if (fHierarchyPresenter != null) { fHierarchyPresenter.uninstall(); fHierarchyPresenter= null; } // if (fForegroundColor != null) { // fForegroundColor.dispose(); // fForegroundColor= null; // } // if (fBackgroundColor != null) { // fBackgroundColor.dispose(); // fBackgroundColor= null; // } // if (fPreferenceStore != null) // fPreferenceStore.removePropertyChangeListener(this); super.unconfigure(); } }