org.eclipse.lsp4e.outline.CNFOutlinePage.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.lsp4e.outline.CNFOutlinePage.java

Source

/*******************************************************************************
 * Copyright (c) 2017 Red Hat Inc. and others.
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *  Mickael Istria (Red Hat Inc.) - initial implementation
 *  Lucas Bullen (Red Hat Inc.) - Bug 520053 - Clicking nodes in the 'Outline' should navigate
 *******************************************************************************/
package org.eclipse.lsp4e.outline;

import org.eclipse.core.runtime.Adapters;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.lsp4e.LanguageServerPlugin;
import org.eclipse.lsp4e.LanguageServiceAccessor.LSPDocumentInfo;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.navigator.CommonViewer;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;

public class CNFOutlinePage implements IContentOutlinePage {

    public static final String ID = "org.eclipse.lsp4e.outline"; //$NON-NLS-1$
    public static final String LINK_WITH_EDITOR_PREFERENCE = ID + ".linkWithEditor"; //$NON-NLS-1$

    private CommonViewer viewer;
    private IEclipsePreferences preferences;
    private LSPDocumentInfo info;
    private ITextEditor textEditor;
    private ITextViewer textEditorViewer;

    class OutlineInfo {

        public final LSPDocumentInfo info;
        public final ITextEditor textEditor;

        public OutlineInfo(LSPDocumentInfo info, @Nullable ITextEditor textEditor) {
            this.info = info;
            this.textEditor = textEditor;
        }
    }

    public CNFOutlinePage(LSPDocumentInfo info, @Nullable ITextEditor textEditor) {
        this.preferences = InstanceScope.INSTANCE.getNode(LanguageServerPlugin.PLUGIN_ID);
        this.textEditor = textEditor;
        this.info = info;
    }

    @Override
    public void createControl(Composite parent) {
        this.viewer = new CommonViewer(ID, parent, SWT.NONE);
        this.viewer.setInput(new OutlineInfo(info, textEditor));
        if (textEditor != null) {
            this.viewer.addOpenListener(event -> {
                if (preferences.getBoolean(LINK_WITH_EDITOR_PREFERENCE, true))
                    textEditor.setFocus();
            });
            if (textEditor instanceof AbstractTextEditor) {
                AbstractTextEditor editor = (AbstractTextEditor) textEditor;
                textEditorViewer = ((ITextViewer) editor.getAdapter(ITextOperationTarget.class));
            } else {
                textEditorViewer = null;
            }
            this.viewer.addSelectionChangedListener(event -> {
                if (preferences.getBoolean(LINK_WITH_EDITOR_PREFERENCE, true) && viewer.getTree().isFocusControl()
                        && viewer.getSelection() != null) {
                    Object selection = ((TreeSelection) viewer.getSelection()).getFirstElement();
                    if (selection != null && selection instanceof SymbolInformation) {
                        Range range = ((SymbolInformation) selection).getLocation().getRange();
                        try {
                            int startLineOffest = info.getDocument().getLineOffset(range.getStart().getLine());
                            textEditor.selectAndReveal(startLineOffest + range.getStart().getCharacter(),
                                    range.getEnd().getCharacter() - range.getStart().getCharacter());
                        } catch (BadLocationException e) {
                            return;
                        }
                    }
                }
            });
            if (textEditorViewer != null) {
                editorSelectionChangedListener = new EditorSelectionChangedListener();
                editorSelectionChangedListener.install(textEditorViewer.getSelectionProvider());
            }
        }
    }

    class EditorSelectionChangedListener implements ISelectionChangedListener {

        public void install(ISelectionProvider selectionProvider) {
            if (selectionProvider == null)
                return;

            if (selectionProvider instanceof IPostSelectionProvider) {
                IPostSelectionProvider provider = (IPostSelectionProvider) selectionProvider;
                provider.addPostSelectionChangedListener(this);
            } else {
                selectionProvider.addSelectionChangedListener(this);
            }
        }

        public void uninstall(ISelectionProvider selectionProvider) {
            if (selectionProvider == null)
                return;

            if (selectionProvider instanceof IPostSelectionProvider) {
                IPostSelectionProvider provider = (IPostSelectionProvider) selectionProvider;
                provider.removePostSelectionChangedListener(this);
            } else {
                selectionProvider.removeSelectionChangedListener(this);
            }
        }

        @Override
        public void selectionChanged(SelectionChangedEvent event) {
            ISelection selection = event.getSelection();
            if (!(selection instanceof ITextSelection)) {
                return;
            }
            ITextSelection textSelection = (ITextSelection) selection;
            if (!preferences.getBoolean(LINK_WITH_EDITOR_PREFERENCE, true)) {
                return;
            }
            int offset = viewer instanceof ITextViewerExtension5
                    ? ((ITextViewerExtension5) viewer).widgetOffset2ModelOffset(textSelection.getOffset())
                    : textSelection.getOffset();
            refreshTreeSelection(viewer, offset, info.getDocument());
        }
    }

    private EditorSelectionChangedListener editorSelectionChangedListener;

    public static void refreshTreeSelection(TreeViewer viewer, int offset, IDocument document) {
        ITreeContentProvider contentProvider = (ITreeContentProvider) viewer.getContentProvider();
        Object[] objects = contentProvider.getElements(null);
        SymbolInformation bestSymbol = null;
        int level = 0;
        while (objects != null && objects.length > 0) {
            SymbolInformation nextChild = null;
            for (Object object : objects) {
                SymbolInformation symbol = object instanceof SymbolInformation ? (SymbolInformation) object
                        : Adapters.adapt(object, SymbolInformation.class);
                if (symbol != null) {
                    if (isOffsetInSymbolRange(offset, symbol, document)) {
                        nextChild = symbol;
                        objects = contentProvider.getChildren(symbol);
                        break;
                    }
                }
            }
            if (nextChild == null)
                break;
            level++;
            bestSymbol = nextChild;
        }
        if (bestSymbol != null) {
            if (bestSymbol.equals(viewer.getStructuredSelection().getFirstElement())) {
                // the symbol to select is the same than current selected symbol, don't select
                // it.
                return;
            }
            final int finalLevel = level;
            final SymbolInformation finalBestSymbol = bestSymbol;
            Display.getDefault().asyncExec(() -> {
                viewer.expandToLevel(finalLevel);
                viewer.setSelection(new StructuredSelection(finalBestSymbol), true);
            });
        }
    }

    private static boolean isOffsetInSymbolRange(int offset, SymbolInformation symbol, IDocument document) {
        Range range = symbol.getLocation().getRange();
        try {
            int startOffset = document.getLineOffset(range.getStart().getLine()) + range.getStart().getCharacter();
            int endOffset = document.getLineOffset(range.getEnd().getLine()) + range.getEnd().getCharacter();
            return startOffset <= offset && endOffset >= offset;
        } catch (BadLocationException e) {
            return false;
        }
    }

    @Override
    public void dispose() {
        this.viewer.dispose();
        if (textEditorViewer != null) {
            editorSelectionChangedListener.uninstall(textEditorViewer.getSelectionProvider());
        }
    }

    @Override
    public Control getControl() {
        return this.viewer.getControl();
    }

    @Override
    public void setActionBars(IActionBars actionBars) {
    }

    @Override
    public void setFocus() {
        this.viewer.getTree().setFocus();
    }

    @Override
    public void addSelectionChangedListener(ISelectionChangedListener listener) {
        this.viewer.addSelectionChangedListener(listener);
    }

    @Override
    public ISelection getSelection() {
        return this.viewer.getSelection();
    }

    @Override
    public void removeSelectionChangedListener(ISelectionChangedListener listener) {
        this.viewer.removeSelectionChangedListener(listener);
    }

    @Override
    public void setSelection(ISelection selection) {
        this.viewer.setSelection(selection);
    }

}