/******************************************************************************* * Copyright (c) 2015 Bruno Medeiros and other Contributors. * 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: * Bruno Medeiros - initial API and implementation *******************************************************************************/ package melnorme.lang.ide.ui.text; import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull; import static melnorme.utilbox.core.Assert.AssertNamespace.assertUnreachable; import static melnorme.utilbox.core.CoreUtil.array; import java.util.Map; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.AbstractInformationControlManager; import org.eclipse.jface.text.DefaultInformationControl; import org.eclipse.jface.text.IAutoEditStrategy; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IInformationControl; import org.eclipse.jface.text.IInformationControlCreator; import org.eclipse.jface.text.ITextHover; import org.eclipse.jface.text.ITextViewerExtension2; import org.eclipse.jface.text.contentassist.ContentAssistant; import org.eclipse.jface.text.contentassist.IContentAssistProcessor; import org.eclipse.jface.text.contentassist.IContentAssistant; import org.eclipse.jface.text.information.IInformationPresenter; import org.eclipse.jface.text.information.IInformationProvider; import org.eclipse.jface.text.information.InformationPresenter; import org.eclipse.jface.text.reconciler.IReconciler; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.IAnnotationHover; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.texteditor.ITextEditor; import _org.eclipse.jdt.internal.ui.text.HTMLAnnotationHover; import melnorme.lang.ide.core.TextSettings_Actual.LangPartitionTypes; import melnorme.lang.ide.core.text.ISourceBufferExt; import melnorme.lang.ide.core.text.TextSourceUtils; import melnorme.lang.ide.core.text.format.FormatterIndentMode; import melnorme.lang.ide.ui.CodeFormatterConstants; import melnorme.lang.ide.ui.EditorSettings_Actual; import melnorme.lang.ide.ui.LangUIPlugin; import melnorme.lang.ide.ui.LangUIPlugin_Actual; import melnorme.lang.ide.ui.editor.LangSourceViewer; import melnorme.lang.ide.ui.editor.ProjectionViewerExt; import melnorme.lang.ide.ui.editor.hover.BestMatchHover; import melnorme.lang.ide.ui.editor.hover.HoverInformationProvider; import melnorme.lang.ide.ui.editor.structure.LangOutlineInformationControl.OutlineInformationControlCreator; import melnorme.lang.ide.ui.editor.structure.StructureElementInformationProvider; import melnorme.lang.ide.ui.text.completion.CompletionProposalsGrouping; import melnorme.lang.ide.ui.text.completion.ContenAssistProcessorExt.NullContentAssistProcessorExt; import melnorme.lang.ide.ui.text.completion.ContentAssistantExt; import melnorme.lang.ide.ui.text.completion.LangContentAssistProcessor; import melnorme.lang.ide.ui.text.completion.LangContentAssistProcessor.ContentAssistCategoriesBuilder; import melnorme.utilbox.collections.Indexable; public abstract class AbstractLangSourceViewerConfiguration extends LangBasicSourceViewerConfiguration { protected final ISourceBufferExt sourceBuffer; /** Editor for this configuration. Can be null. It is preferred that sourceBuffer be used instead of this, * but some legacy or third-party API requires the editor itself */ protected final ITextEditor editor_opt; // can be null public AbstractLangSourceViewerConfiguration(IPreferenceStore preferenceStore, ISourceBufferExt sourceBuffer, ITextEditor editor) { super(preferenceStore); this.sourceBuffer = assertNotNull(sourceBuffer); this.editor_opt = editor; } public ITextEditor getEditor_orNull() { return editor_opt; } /* ----------------- Hovers ----------------- */ @Override public final ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) { return getTextHover(sourceViewer, contentType, ITextViewerExtension2.DEFAULT_HOVER_STATE_MASK); } @Override public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType, int stateMask) { return getBestMatchHover(); } protected BestMatchHover getBestMatchHover() { return new BestMatchHover(sourceBuffer, getEditor_orNull()); } @Override public IAnnotationHover getAnnotationHover(ISourceViewer sourceViewer) { return new HTMLAnnotationHover(false) { @Override protected boolean isIncluded(Annotation annotation) { return isShowInVerticalRuler(annotation); } }; } @Override public IAnnotationHover getOverviewRulerAnnotationHover(ISourceViewer sourceViewer) { return new HTMLAnnotationHover(true) { @Override protected boolean isIncluded(Annotation annotation) { return isShowInOverviewRuler(annotation); } }; } @Override public IInformationPresenter getInformationPresenter(ISourceViewer sourceViewer) { InformationPresenter presenter = new InformationPresenter(getInformationPresenterControlCreator(sourceViewer)); presenter.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer)); // Register information providers for (String contentType : getConfiguredContentTypes(sourceViewer)) { presenter.setInformationProvider(getInformationProvider(contentType, sourceViewer), contentType); } presenter.setSizeConstraints(100, 12, false, true); return presenter; } // ================ Information provider @SuppressWarnings("unused") protected IInformationProvider getInformationProvider(String contentType, ISourceViewer sourceViewer) { return new HoverInformationProvider(getBestMatchHover()); } protected IInformationControlCreator getInformationPresenterControlCreator( @SuppressWarnings("unused") ISourceViewer sourceViewer) { return new IInformationControlCreator() { @Override public IInformationControl createInformationControl(Shell parent) { return new DefaultInformationControl(parent, true); } }; } /* ----------------- Navigation operations ----------------- */ @Override protected Map<String, IAdaptable> getHyperlinkDetectorTargets(ISourceViewer sourceViewer) { Map<String, IAdaptable> targets = super.getHyperlinkDetectorTargets(sourceViewer); targets.put(EditorSettings_Actual.EDITOR_CODE_TARGET, editor_opt); return targets; } public void installOutlinePresenter(final LangSourceViewer sourceViewer) { InformationPresenter presenter = new InformationPresenter(getOutlinePresenterControlCreator(sourceViewer)); presenter.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer)); presenter.setAnchor(AbstractInformationControlManager.ANCHOR_GLOBAL); IInformationProvider provider = new StructureElementInformationProvider(sourceBuffer); for(String contentType : getConfiguredContentTypes(sourceViewer)) { presenter.setInformationProvider(provider, contentType); } presenter.setSizeConstraints(50, 20, true, false); presenter.install(sourceViewer); sourceViewer.setOutlinePresenter(presenter); } protected IInformationControlCreator getOutlinePresenterControlCreator( @SuppressWarnings("unused") ISourceViewer sourceViewer) { return new OutlineInformationControlCreator(this); } /* ----------------- Modification operations ----------------- */ @Override public String[] getDefaultPrefixes(ISourceViewer sourceViewer, String contentType) { return new String[] { getToggleCommentPrefix(), "" }; } protected abstract String getToggleCommentPrefix(); @Override public IAutoEditStrategy[] getAutoEditStrategies(ISourceViewer sourceViewer, String contentType) { if(IDocument.DEFAULT_CONTENT_TYPE.equals(contentType)) { return array(new AutoEditStrategyAdapter( LangUIPlugin_Actual.createAutoEditStrategy(contentType, new VerifyKeyRecorder(sourceViewer)) )); } else { return super.getAutoEditStrategies(sourceViewer, contentType); } } @Override public String[] getIndentPrefixes(ISourceViewer sourceViewer, String contentType) { FormatterIndentMode indentMode = CodeFormatterConstants.fromPrefStore(); int spaceIndentationSize = CodeFormatterConstants.FORMATTER_INDENTATION_SPACES_SIZE.get(); String spaceIndent = TextSourceUtils.getNSpaces(spaceIndentationSize); // An empty string must be part of IndentPrefixes, so that empty lines do not fail the unindent operation. // for indent operation, only first element will be used, I believe switch (indentMode) { case TAB: return array("\t", spaceIndent, ""); // return getIndentPrefixesForTab(spaceIndent); case SPACES: return array(spaceIndent, "\t", ""); // return getIndentPrefixesForSpaces(spaceIndent); } throw assertUnreachable(); } @Override protected void updateIndentationSettings(SourceViewer sourceViewer, String property) { super.updateIndentationSettings(sourceViewer, property); if( CodeFormatterConstants.FORMATTER_INDENTATION_SPACES_SIZE.key.equals(property) || CodeFormatterConstants.FORMATTER_INDENT_MODE.key.equals(property)) { for(String contentType : getConfiguredContentTypes(sourceViewer)) { String[] prefixes= getIndentPrefixes(sourceViewer, contentType); sourceViewer.setIndentPrefixes(prefixes, contentType); } } } /* ----------------- Content Assist ----------------- */ @Override public ContentAssistant getContentAssistant(ISourceViewer sourceViewer) { if(sourceViewer instanceof LangSourceViewer) { LangSourceViewer langSourceViewer = (LangSourceViewer) sourceViewer; ContentAssistantExt assistant = createContentAssitant(langSourceViewer); assistant.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer)); assistant.setRestoreCompletionProposalSize(LangUIPlugin.getDialogSettings("completion_proposal_size")); assistant.setInformationControlCreator( getInformationControl_ContentAsssist(getAdditionalInfoAffordanceString())); assistant.setContextInformationPopupOrientation(IContentAssistant.CONTEXT_INFO_ABOVE); assistant.enableColoredLabels(true); configureContentAssistantProcessors(assistant); // Note: configuration must come after processors are created assistant.configure(); return assistant; } return null; } protected ContentAssistantExt createContentAssitant(LangSourceViewer langSourceViewer) { return new ContentAssistantExt(getPreferenceStore(), langSourceViewer); } protected void configureContentAssistantProcessors(ContentAssistantExt assistant) { Indexable<CompletionProposalsGrouping> categories = getContentAssistCategoriesProvider().getCategories(); IContentAssistProcessor defaultCAP = createContentAssistProcessor(assistant, categories); for (LangPartitionTypes partitionType : LangPartitionTypes.values()) { IContentAssistProcessor cap; if(partitionType.getId().equals(IDocument.DEFAULT_CONTENT_TYPE)) { cap = defaultCAP; } else { // Setup a dummy content Assist processor, to prevent a platform NPE when returning null proposals // See: https://git.eclipse.org/r/#/c/76121/ cap = new NullContentAssistProcessorExt(); } assistant.setContentAssistProcessor(cap, partitionType.getId()); } } protected LangContentAssistProcessor createContentAssistProcessor(ContentAssistantExt assistant, Indexable<CompletionProposalsGrouping> categories) { return new LangContentAssistProcessor(assistant, categories, sourceBuffer, getEditor_orNull()); } protected abstract ContentAssistCategoriesBuilder getContentAssistCategoriesProvider(); /* ----------------- reconciler ----------------- */ @Override public IReconciler getReconciler(ISourceViewer sourceViewer) { return null; // not used, disable the textual spellchecker too } /* ----------------- ----------------- */ @Override public void configureViewer(ProjectionViewerExt sourceViewer) { super.configureViewer(sourceViewer); if(sourceViewer instanceof LangSourceViewer) { LangSourceViewer langSourceViewer = (LangSourceViewer) sourceViewer; installOutlinePresenter(langSourceViewer); } } }