com.aptana.ide.logging.view.LogTab.java Source code

Java tutorial

Introduction

Here is the source code for com.aptana.ide.logging.view.LogTab.java

Source

/** 
 * This file Copyright (c) 2005-2008 Aptana, Inc. This program is
 * dual-licensed under both the Aptana Public License and the GNU General
 * Public license. You may elect to use one or the other of these licenses.
 * 
 * This program is distributed in the hope that it will be useful, but
 * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
 * NONINFRINGEMENT. Redistribution, except as permitted by whichever of
 * the GPL or APL you select, is prohibited.
 *
 * 1. For the GPL license (GPL), you can redistribute and/or modify this
 * program under the terms of the GNU General Public License,
 * Version 3, as published by the Free Software Foundation.  You should
 * have received a copy of the GNU General Public License, Version 3 along
 * with this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Aptana provides a special exception to allow redistribution of this file
 * with certain Eclipse Public Licensed code and certain additional terms
 * pursuant to Section 7 of the GPL. You may view the exception and these
 * terms on the web at http://www.aptana.com/legal/gpl/.
 * 
 * 2. For the Aptana Public License (APL), this program and the
 * accompanying materials are made available under the terms of the APL
 * v1.0 which accompanies this distribution, and is available at
 * http://www.aptana.com/legal/apl/.
 * 
 * You may view the GPL, Aptana's exception and additional terms, and the
 * APL in the file titled license.html at the root of the corresponding
 * plugin containing this source file.
 * 
 * Any modifications to this file must keep this entire header intact.
 */
package com.aptana.ide.logging.view;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.resource.FontRegistry;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.text.source.IAnnotationAccess;
import org.eclipse.jface.text.source.IOverviewRuler;
import org.eclipse.jface.text.source.ISharedTextColors;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.OverviewRuler;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.text.source.VerticalRuler;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.LineStyleEvent;
import org.eclipse.swt.custom.LineStyleListener;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.custom.TextChangeListener;
import org.eclipse.swt.custom.TextChangedEvent;
import org.eclipse.swt.custom.TextChangingEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.internal.editors.text.EditorsPlugin;
import org.eclipse.ui.texteditor.AnnotationPreference;
import org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess;
import org.eclipse.ui.texteditor.IUpdate;
import org.eclipse.ui.texteditor.MarkerAnnotationPreferences;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;

import com.aptana.ide.core.IdeLog;
import com.aptana.ide.editors.UnifiedEditorsPlugin;
import com.aptana.ide.editors.unified.UnifiedColorManager;
import com.aptana.ide.editors.unified.UniformResourceMarkerAnnotationModel;
import com.aptana.ide.lexer.ILexer;
import com.aptana.ide.lexer.Lexeme;
import com.aptana.ide.lexer.LexemeList;
import com.aptana.ide.logging.ILogResource;
import com.aptana.ide.logging.ILogResourceListener;
import com.aptana.ide.logging.ILogTailListener;
import com.aptana.ide.logging.ILogWatcher;
import com.aptana.ide.logging.LogResourceFactory;
import com.aptana.ide.logging.LoggingPlugin;
import com.aptana.ide.logging.LoggingPreferences;
import com.aptana.ide.logging.Messages;
import com.aptana.ide.logging.coloring.LoggingColorizer;
import com.aptana.ide.logging.coloring.LoggingLexemeManager;
import com.aptana.ide.logging.coloring.TokenTypes;
import com.aptana.ide.logging.impl.DisplayThreadProxy;
import com.aptana.ide.logging.preferences.ILoggingPreferenceListener;
import com.aptana.ide.logging.preferences.LoggingStructureProvider;
import com.aptana.ide.logging.view.LogView.TextListener;

/**
 * Log tab.
 * @author Denis Denisenko
 */
class LogTab {
    /**
     * Log source viewer.
     * @author Denis Denisenko
     *
     */
    static class LogSourceViewer extends SourceViewer {

        public LogSourceViewer(Composite parent, IVerticalRuler verticalRuler, IOverviewRuler overviewRuler,
                boolean showAnnotationsOverview, int styles) {
            super(parent, verticalRuler, overviewRuler, showAnnotationsOverview, styles);
        }

        /**
         * Reveals the given range of the visible document.
         *
         * @param start the start offset of the range
         * @param end the end offset of the range
         */
        protected void revealLastLine() {

            try {

                IDocument doc = getVisibleDocument();

                int endLine = doc.getLineOfOffset(doc.getLength() == 0 ? 0 : doc.getLength() - 1);
                int startLine = endLine;

                int top = getTextWidget().getTopIndex();
                if (top > -1) {

                    // scroll vertically
                    int bottom = getBottomIndex(getTextWidget());
                    int lines = bottom - top;

                    // if the widget is not scrollable as it is displaying the entire content
                    // setTopIndex won't have any effect.

                    if (startLine >= top && startLine <= bottom && endLine >= top && endLine <= bottom) {

                        // do not scroll at all as it is already visible

                    } else {

                        int delta = Math.max(0, lines - (endLine - startLine));
                        getTextWidget().setTopIndex(startLine - delta / 3);
                        updateViewportListeners(INTERNAL);
                    }
                }
            } catch (BadLocationException e) {
                throw new IllegalArgumentException("Illegal text range"); //$NON-NLS-1$
            }
        }

        /**
         * Returns the last fully visible line of the widget. The exact semantics of "last fully visible
         * line" are:
         * <ul>
         * <li>the last line of which the last pixel is visible, if any
         * <li>otherwise, the only line that is partially visible
         * </ul>
         * 
         * @param widget the widget
         * @return the last fully visible line
         */
        public static int getBottomIndex(StyledText widget) {
            int lastPixel = computeLastVisiblePixel(widget);

            // bottom is in [0 .. lineCount - 1]
            int bottom = widget.getLineIndex(lastPixel);

            // bottom is the first line - no more checking
            if (bottom == 0)
                return bottom;

            int pixel = widget.getLinePixel(bottom);
            // bottom starts on or before the client area start - bottom is the only visible line
            if (pixel <= 0)
                return bottom;

            int offset = widget.getOffsetAtLine(bottom);
            int height = widget.getLineHeight(offset);

            // bottom is not showing entirely - use the previous line
            if (pixel + height - 1 > lastPixel)
                return bottom - 1;

            // bottom is fully visible and its last line is exactly the last pixel
            return bottom;
        }

        /**
         * Returns the last visible pixel in the widget's client area.
         * 
         * @param widget the widget
         * @return the last visible pixel in the widget's client area
         */
        private static int computeLastVisiblePixel(StyledText widget) {
            int caHeight = widget.getClientArea().height;
            int lastPixel = caHeight - 1;

            return lastPixel;
        }
    }

    /**
     * Document writer.
     * @LogTab
     * @author Denis Denisenko
     */
    private static class DocumentWriter extends Writer {
        /**
         * Document.
         */
        private IDocument document;

        /**
         * DocumentWriter constructor.
         * @param document - document.
         */
        public DocumentWriter(IDocument document) {
            this.document = document;
        }

        /**
          * {@inheritDoc}
          */
        @Override
        public void close() throws IOException {
        }

        /**
          * {@inheritDoc}
          */
        @Override
        public void flush() throws IOException {
        }

        /**
          * {@inheritDoc}
          */
        @Override
        public void write(char[] cbuf, int off, int len) throws IOException {
            try {
                document.replace(document.getLength(), 0, new String(cbuf, off, len));
            } catch (BadLocationException e) {
                throw new IOException(e.getMessage());
            }
        }

    }

    /**
     * DEFAULT_ENCODING
     */
    private static final String DEFAULT_ENCODING = "cp1251"; //$NON-NLS-1$

    /**
     * MAX_NOTIFICATION_SIZE
     */
    private static final int MAX_NOTIFICATION_SIZE = 1024;

    /**
     * Book mark annotation type.
     */
    private static final String BOOKMARK_ANNOTATION_TYPE = "org.eclipse.ui.workbench.texteditor.bookmark"; //$NON-NLS-1$

    /**
     * Default font constant. 
     */
    private static final String DEFAULT_FONT = "DEFAULT_TAIL_VIEW_FONT"; //$NON-NLS-1$

    /**
     * Log tab prefix.
     */
    private static final String PREFIX = "LogTab"; //$NON-NLS-1$

    /**
     * Log tab prefix.
     */
    private static final String BOOKMARK_PREFIX = PREFIX + ".BookmarkAction"; //$NON-NLS-1$

    /**
     *  The width of the vertical ruler.
     */
    protected final static int VERTICAL_RULER_WIDTH = 12;

    /**
     * Max colorization columns.
     */
    protected int _maxColorizingColumns = 512;

    /**
     * logView
     */
    private final LogView logView;

    /**
     * URI.
     */
    private final URI uri;

    /**
     * Tab item.
     */
    private CTabItem item;

    /**
     * Ruler.
     */
    private IVerticalRuler ruler;

    /**
     * Current global offset.
     */
    private long currentGlobalOffset = 0;

    /**
     * Content dependent actions.
     */
    private Set contentDependentActions = new HashSet();

    /**
     * Log watcher.
     */
    private ILogWatcher watcher;

    /**
     * Resource.
     */
    private ILogResource resource = null;

    /**
     * Menu manager.
     */
    private MenuManager menuMgr;

    /**
     * Font registry.
     */
    private FontRegistry fontRegistry = new FontRegistry();

    /**
     * Color registry.
     */
    private ColorRegistry colorRegistry = new ColorRegistry();

    /**
     * Viewer.
     */
    private SourceViewer viewer;

    /**
     * Bold caption font.
     */
    private Font boldFont;

    /**
     * Normal caption font.
     */
    private Font normalFont;

    /**
     * Italic caption font.
     */
    private Font italicFont;

    /**
     * Text foreground color.
     */
    private Color textForeground;

    /**
     * Status filed that indicates whether tab has unread data available.
     */
    private volatile boolean unreadDataAvailable;

    /**
     * Whether resource is available.
     */
    private volatile boolean resourceAvailable;

    /**
     * Add Bookmark action.
     */
    private LoggingMarkerRulerAction actionAddBookmark;

    /**
     * Annotation model.
     */
    private UniformResourceMarkerAnnotationModel model;

    /**
     * Overview ruler.
     */
    private IOverviewRuler overviewRuler;

    /**
     * Annotation preferences.
     */
    private MarkerAnnotationPreferences fAnnotationPreferences;

    /**
     * Bookmark color.
     */
    private Color bookmarkColor = new Color(Display.getCurrent(), 0, 0, 255);

    /**
     * Line style listener.
     */
    private LineStyleListener _lineStyleListener;

    /**
     * Logging colorizer.
     */
    private LoggingColorizer _colorizer;

    /**
     * Text change listener.
     */
    private TextChangeListener _textChangeListener;

    /**
     * Lexer.
     */
    private ILexer lexer;

    /**
     * Whether to follow document tail.
     */
    private boolean followTail = true;

    /**
     * Document.
     */
    private IDocument document;

    /**
     * Colorization preference listener.
     */
    private IPropertyChangeListener colorizationPreferencesListener;

    /**
     * Logging preference listener.
     */
    private ILoggingPreferenceListener loggingPreferenceListener;

    /**
     * Tail listener.
     */
    private ILogTailListener tailListener;

    /**
     * Helper for handling decoration.
     */
    protected SourceViewerDecorationSupport decorationSupport;

    /**
     * Logging lexeme manager.
     */
    protected LoggingLexemeManager lexememanager;

    /**
     * Composite.
     */
    private Composite composite;

    /**
     * Whether the tab supports log erasing.
     */
    private boolean supportsErase;

    /**
     * LogView.LogTab constructor.
     * @param uri - URI.
     * @param logView - log view that contains this tab.
     * @param name - tab name. maybe null.
     * @param supportsErase - whether the tab's log supports log erasing.
     */
    public LogTab(LogView logView, URI uri, String name, boolean supportsErase) {
        this.logView = logView;
        this.uri = uri;
        this.supportsErase = supportsErase;
        // creating tab item
        String tabName = name;
        if (tabName == null) {
            tabName = getTabName(uri.getPath());
        }

        fAnnotationPreferences = EditorsPlugin.getDefault().getMarkerAnnotationPreferences();

        item = createLogTabItem(tabName, uri);

        createColorizer();

        try {
            resource = LogResourceFactory.createResource(uri);
            resource.setEncoding(
                    Charset.forName(LoggingPlugin.getDefault().getLoggingPreferences().getDefaultEncoding()));
        } catch (IOException e) {
            //e.printStackTrace(new PrintWriter(writer));
            e.printStackTrace(new PrintWriter(new DocumentWriter(getDocument())));
            return;
        }

        initAnnotationModel();
        createActions();

        hookRulerContextMenu();

        initializeFonts();
        initializeColors();

        getDocument().addDocumentListener(new IDocumentListener() {

            public void documentAboutToBeChanged(DocumentEvent event) {
            }

            public void documentChanged(DocumentEvent event) {
            }
        });

        LoggingPreferences preferences = LoggingPlugin.getDefault().getLoggingPreferences();
        int timeout = preferences.getReadTimeout();
        int readBuffer = preferences.getReadBuffer();

        int maxBytesPerSecond = (int) ((((long) readBuffer) * 1000l) / ((long) timeout));
        watcher = resource.getResourceWatcher(new DisplayThreadProxy(item.getDisplay()), maxBytesPerSecond,
                readBuffer, timeout, Charset.forName(DEFAULT_ENCODING),
                LoggingPlugin.getDefault().getLoggingPreferences().getBacklogLines());

        //registering tail listener
        registerTailListener();

        //registering resource listener
        registerResourceListener();

        //starting watching
        watcher.startWatching();

        //settign selection to the new tab
        this.logView.tabFolder.setSelection(item);

        linkColorer();
    }

    /**
     * Gets tab URI.
     * @return tab URI.
     */
    public URI getURI() {
        return uri;
    }

    /**
     * Gets viewer.
     * @return viewer.
     */
    public TextViewer getViewer() {
        return viewer;
    }

    /**
     * Gets tab name.
     * @return tab name.
     */
    public String getName() {
        return item.getText();
    }

    /**
     * Gets whether the tab's log supports log erase.
     * @return whether the tab's log supports log erase.
     */
    public boolean supportsLogErase() {
        return this.supportsErase;
    }

    /**
     * Sets tab name.
     * @param name - name to set.
     */
    public void setName(String name) {
        item.setText(name);
    }

    /**
     * Closes the tab.
     */
    public void close() {
        watcher.stopWatching();
        watcher.close();
        //console.destroy();
        try {
            resource.close();
        } catch (IOException e) {
            IdeLog.logError(LoggingPlugin.getDefault(), com.aptana.ide.logging.view.Messages.LogTab_4, e);
        }

        disposeListeners();

        if (boldFont != null) {
            boldFont.dispose();
        }

        if (italicFont != null) {
            italicFont.dispose();
        }

        bookmarkColor.dispose();

        item.dispose();

        if (decorationSupport != null) {
            decorationSupport.dispose();
            decorationSupport = null;
        }
    }

    /**
     * Starts watching.
     */
    public void start() {
        watcher.startWatching();
    }

    /**
     * Stops watching.
     */
    public void stop() {
        watcher.stopWatching();
    }

    /**
     * Whether watching is on.
     * @return true if watching, false otherwise.
     */
    public boolean isWatching() {
        return watcher.watchingStatus();
    }

    /**
     * Reloads the log. 
     */
    public void reload() {
        watcher.resetWatching();
        clear();
        watcher.startWatching();
    }

    /**
     * Gets tab item.
     * @return tab item.
     */
    public CTabItem getItem() {
        return item;
    }

    /**
     * Clears tab. 
     */
    public void clear() {
        if (document != null) {
            document.set(""); //$NON-NLS-1$
        }
        //currentLexemes.clear();
        currentGlobalOffset = 0;
        if (lexememanager != null) {
            List<String> empty = Collections.emptyList();
            lexememanager.dataAvailable(empty);
        }
    }

    /**
     * Notifies current tab, it is selected.
     */
    public void selected() {
        //removing "unread data state" if any
        unreadDataAvailable = false;
        makeTabBold(false);

        if (!resourceAvailable) {
            makeTabShaded(true);
        }
    }

    /**
     * Clears log file.
     */
    public void clearLogFile() {
        try {
            resource.clear();
        } catch (IOException e) {
            MessageDialog.openError(this.getItem().getControl().getShell(),
                    com.aptana.ide.logging.view.Messages.LogTab_2, com.aptana.ide.logging.view.Messages.LogTab_1 //$NON-NLS-2$
                            + resource.getURI().getPath());
        }
        reload();
    }

    /**
     * Gets whether unread data is available. 
     * @return whether unread data is available.
     */
    public boolean hasUnreadData() {
        return unreadDataAvailable;
    }

    /**
     * Sets whether viewer follows document tail.
     * @param followTail - whether to follow tail.
     */
    public void setFollowTail(boolean followTail) {
        this.followTail = followTail;
    }

    /**
     * Inverts whether viewer follows document tail.
     */
    public void invertFollowTail() {
        followTail = !followTail;
    }

    /**
     * Gets selection.
     * @return selection or null
     */
    String getSelection() {
        if (viewer == null) {
            return null;
        }
        TextSelection selection = (TextSelection) viewer.getSelection();
        if (selection == null) {
            return null;
        }

        try {
            return getDocument().get(selection.getOffset(), selection.getLength());
        } catch (BadLocationException ex) {
            IdeLog.logError(LoggingPlugin.getDefault(), com.aptana.ide.logging.view.Messages.LogTab_ERR_Exception,
                    ex);
            return null;
        }
    }

    /**
     * Refreshes tab viewer. 
     */
    void refreshViewer() {
        viewer.refresh();
    }

    /**
     * Creates new logger tab item
     * 
     * @param tabName -
     *            tab name.
     * @return tab item
     */
    private CTabItem createLogTabItem(String tabName, URI uri) {
        CTabItem item = new CTabItem(this.logView.tabFolder, SWT.DEFAULT);
        item.setText(tabName);
        if (uri != null) {
            item.setToolTipText(uri.getPath());
        }

        document = new Document();
        lexememanager = new LoggingLexemeManager(document, LoggingPlugin.getDefault().getLoggingPreferences());

        // creating a view
        IAnnotationAccess access = new DefaultMarkerAnnotationAccess();
        ruler = createVerticalRuler(access);
        overviewRuler = createOverviewRuler(access);

        createViewer(item);

        return item;
    }

    /**
     * Recreates viewer.
     */
    void recreateViewer() {
        decorationSupport.dispose();
        decorationSupport = null;
        composite.dispose();
        viewer = null;

        LoggingPlugin.getDefault().getLoggingPreferences().removePreferenceListener(loggingPreferenceListener);
        createViewer(item);
    }

    /**
     * @param item
     */
    private void createViewer(CTabItem item) {
        int viewerStyle = 0;
        if (LoggingPlugin.getDefault().getLoggingPreferences().getWrapping()) {
            viewerStyle = SWT.V_SCROLL | SWT.WRAP | SWT.BORDER;
        } else {
            viewerStyle = SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER;
        }

        composite = new Composite(this.logView.tabFolder, SWT.NONE);
        GridLayout layout = new GridLayout(1, true);
        layout.marginHeight = 3;
        layout.marginWidth = 3;
        GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
        composite.setLayout(layout);
        composite.setLayoutData(data);
        composite.setBackground(UnifiedColorManager.getInstance().getColor(new RGB(220, 220, 220)));

        viewer = new LogSourceViewer(composite, ruler, overviewRuler, true, viewerStyle);
        viewer.addSelectionChangedListener(new ISelectionChangedListener() {

            public void selectionChanged(SelectionChangedEvent event) {
                // TODO Auto-generated method stub
                int i = 0;
                i++;
            }

        });
        viewer.setDocument(getDocument());

        Control control = viewer.getControl();
        control.setLayoutData(data);

        viewer.getTextWidget().setFont(LoggingPlugin.getDefault().getLoggingPreferences().getFont());

        item.setControl(composite);

        //adding logger menu
        Menu menu = logView.menuMgr.createContextMenu(viewer.getTextWidget());
        viewer.getTextWidget().setMenu(menu);

        TextListener listener = this.logView.new TextListener(viewer);
        viewer.addTextInputListener(listener);
        viewer.addTextListener(listener);

        viewer.activatePlugins();

        createSourceViewerDecorationSupport(viewer);

        bindToColorizationSave(viewer);

        //PreferenceUtils.registerFontPreference(viewer.getTextWidget(), LoggingPreferences.MAIN_TEXT_FONT_KEY);
    }

    /**
     * Recreates tab item.
     * @param name - tab name.
     * @param newPos - new position.
     */
    void recreateItem(String name, int newPos) {
        item = new CTabItem(this.logView.tabFolder, SWT.DEFAULT, newPos);
        item.setText(name);
        item.setControl(composite);
    }

    /**
     * Binds to colorization save.
     * @param viewer - viewer.
     */
    private void bindToColorizationSave(final SourceViewer viewer) {
        colorizationPreferencesListener = new IPropertyChangeListener() {

            public void propertyChange(PropertyChangeEvent event) {
                if (LoggingStructureProvider.COLORIZATION_SAVED.equals(event.getProperty())) {
                    createColorizer();

                    lexememanager.clearCache();
                    if (viewer != null) {
                        viewer.setRedraw(false);
                        ((StyledText) LogTab.this.viewer.getTextWidget())
                                .removeLineStyleListener(_lineStyleListener);
                        ((StyledText) LogTab.this.viewer.getTextWidget()).addLineStyleListener(_lineStyleListener);
                        viewer.setRedraw(true);
                    }
                }
            }

        };
        UnifiedEditorsPlugin.getDefault().getPreferenceStore()
                .addPropertyChangeListener(colorizationPreferencesListener);

        LoggingPreferences preferences = LoggingPlugin.getDefault().getLoggingPreferences();
        loggingPreferenceListener = new ILoggingPreferenceListener() {

            public void rulesChanged() {
            }

            public void wrappingChanged(boolean wrapping) {
                recreateViewer();
            }

            public void fontChanged(Font font) {
                viewer.getTextWidget().setFont(font);
            }

            public void textForegroundColorChanged(Color color) {
                textForeground = color;
            }
        };
        preferences.addPreferenceListener(loggingPreferenceListener);
    }

    /**
     * Hooks context menu.
     */
    private void hookRulerContextMenu() {
        menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
        menuMgr.setRemoveAllWhenShown(true);
        menuMgr.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager manager) {
                logView.setFocus();
                fillRulerContextMenu(manager);
            }
        });

        Menu menu = menuMgr.createContextMenu(ruler.getControl());
        ruler.getControl().setMenu(menu);

        logView.getSite().registerContextMenu(menuMgr, viewer.getSelectionProvider());
    }

    /**
     * Fills context menu.
     * @param manager - menu manager.
     */
    private void fillRulerContextMenu(IMenuManager manager) {
        manager.add(actionAddBookmark);
        //        manager.add(actionAdd);
        //        manager.add(actionDelete);
        //        
        //        manager.add(new Separator());
        //        
        //        manager.add(actionFindReplace);
        //        
        //        manager.add(new Separator());
        //        // Other plug-ins can contribute there actions here
        //        manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
    }

    /**
     * Creates vertical ruler.
     *
     * @param access - annotation access.
     * 
     * @return the vertical ruler
     */
    protected IVerticalRuler createVerticalRuler(IAnnotationAccess access) {
        return new VerticalRuler(VERTICAL_RULER_WIDTH, access);
    }

    /**
     * Creates overview ruler.
     * 
     * @param access - annotation access.
     * 
     * @return the overview ruler
     */
    protected IOverviewRuler createOverviewRuler(IAnnotationAccess access) {
        ISharedTextColors sharedColors = EditorsPlugin.getDefault().getSharedTextColors();
        IOverviewRuler ruler = new OverviewRuler(access, VERTICAL_RULER_WIDTH, sharedColors);
        Iterator e = fAnnotationPreferences.getAnnotationPreferences().iterator();
        while (e.hasNext()) {
            AnnotationPreference preference = (AnnotationPreference) e.next();
            if (preference.contributesToHeader())
                ruler.addHeaderAnnotationType(preference.getAnnotationType());
        }

        String type = BOOKMARK_ANNOTATION_TYPE;

        ruler.addAnnotationType(type);
        ruler.addHeaderAnnotationType(type);
        ruler.setAnnotationTypeLayer(type, 1);
        //TODO find why default bookmark preferences fail to be loaded correctly. 
        ruler.setAnnotationTypeColor(type, bookmarkColor);
        return ruler;
    }

    /**
     * Gets tab name by path.
     * 
     * @param path -
     *            path.
     * @return tab name.
     */
    private String getTabName(String path) {
        Path p = new Path(path);
        return p.lastSegment();
    }

    /**
     * Makes tab bold.
     * @param bold - whether to set bold or to remove.
     */
    private void makeTabBold(boolean bold) {
        Font currentFont = item.getFont();

        int style = currentFont.getFontData()[0].getStyle();
        if (bold) {
            if ((style & SWT.BOLD) != 0) {
                return;
            }
            item.setFont(boldFont);
            //item.getControl().setFont(boldFont);
        } else {
            if ((style & SWT.BOLD) == 0) {
                return;
            }
            item.setFont(normalFont);
            //item.getControl().setFont(normalFont);
        }
    }

    /**
     * Makes tab shaded.
     * @param shaded - true if to turn shaded mode on, false otherwise.
     */
    private void makeTabShaded(boolean shaded) {
        if (shaded) {
            Font currentFont = item.getFont();
            if (currentFont.equals(italicFont)) {
                return;
            }

            item.setFont(italicFont);
        } else {
            Font currentFont = item.getFont();
            if (!currentFont.equals(italicFont)) {
                return;
            }

            item.setFont(normalFont);
        }
    }

    /**
     * Initialized tab fonts.
     */
    private void initializeColors() {
        textForeground = LoggingPlugin.getDefault().getLoggingPreferences().getTextColor();
    }

    /**
     * Initialized tab fonts.
     */
    private void initializeFonts() {
        Font normalFont = item.getFont();

        //setting normal font
        this.normalFont = normalFont;

        //setting bold font
        FontData data = normalFont.getFontData()[0];
        int originalStyle = data.getStyle();
        int boldStyle = originalStyle | SWT.BOLD;
        data.setStyle(boldStyle);
        this.boldFont = new Font(normalFont.getDevice(), new FontData[] { data });

        //setting italic font
        int italicStyle = originalStyle | SWT.ITALIC;
        data = normalFont.getFontData()[0];
        data.setStyle(italicStyle);
        this.italicFont = new Font(normalFont.getDevice(), new FontData[] { data });
    }

    private void createActions() {
        actionAddBookmark = new LoggingMarkerRulerAction(Messages.getResourceBundle(), BOOKMARK_PREFIX, resource,
                getDocument(), model, ruler, true, IMarker.BOOKMARK, item.getControl().getShell());
        actionAddBookmark.update();
    }

    /**
     * Initializes annotation model.
     * @return model
     */
    private void initAnnotationModel() {
        model = new UniformResourceMarkerAnnotationModel(resource);
        model.connect(getDocument());
        ruler.setModel(model);
        overviewRuler.setModel(model);
    }

    /**
     * Registers resource listener.
     */
    private void registerResourceListener() {
        watcher.registerListener(new ILogResourceListener() {
            public void resourceAvailable(boolean available) {
                resourceAvailable = available;
                //if resource became unavailable and we're not in bold mode,
                //shading tab caption.
                if (!unreadDataAvailable) {
                    if (!resourceAvailable) {
                        makeTabShaded(true);
                    } else {
                        makeTabShaded(false);
                    }
                }
            }
        });
    }

    /**
     * Registers tail listener.
     */
    private void registerTailListener() {
        tailListener = new ILogTailListener() {

            public void dataAvailable(String data, long globalOffset, long globalLength) {
                updateData(data, globalOffset, globalLength);
            }

            public void errorHappened(Throwable th) {
                IdeLog.logError(LoggingPlugin.getDefault(),
                        com.aptana.ide.logging.view.Messages.LogTab_ERR_FetchTail, th);
                //th.printStackTrace(new PrintWriter(new DocumentWriter(getDocument())));
            }

        };
        watcher.registerListener(tailListener);
    }

    /**
     * Updates content dependent actions.
     */
    private void updateContentDependentActions() {
        Iterator it = contentDependentActions.iterator();
        while (it.hasNext()) {
            Action action = (Action) it.next();
            if (action instanceof IUpdate) {
                ((IUpdate) action).update();
            }
        }
    }

    /**
     * Marks action as content dependent.
     * @param action - action to mark.
     */
    void markAsContentDependent(Action action) {
        contentDependentActions.add(action);
    }

    /**
     * Links colorer.
     */
    private void linkColorer() {
        if (_lineStyleListener == null) {
            _lineStyleListener = new LineStyleListener() {
                /**
                 * @see org.eclipse.swt.custom.LineStyleListener#lineGetStyle(org.eclipse.swt.custom.LineStyleEvent)
                 */
                public void lineGetStyle(LineStyleEvent e) {
                    //LexemeList lexemeList = currentLexemes;

                    int orgOffset = e.lineOffset;
                    int offset = orgOffset;
                    int extra = 0;
                    int lineLength = e.lineText.length();

                    // need to get actual offset values in the doc,
                    // as widget offsets do not include potentially folded code
                    if (viewer instanceof ITextViewerExtension5) {
                        ITextViewerExtension5 v5 = (ITextViewerExtension5) viewer;
                        offset = v5.widgetOffset2ModelOffset(e.lineOffset);
                        extra = offset - e.lineOffset;
                    }

                    int maxLineLength = lineLength > _maxColorizingColumns ? _maxColorizingColumns : lineLength;
                    Lexeme[] lexemes = null;

                    int lineNumber;
                    try {
                        lineNumber = document.getLineOfOffset(e.lineOffset);
                        lexemes = lexememanager.getLexemes(lineNumber);
                    } catch (BadLocationException e1) {
                        IdeLog.logError(LoggingPlugin.getDefault(),
                                com.aptana.ide.logging.view.Messages.LogTab_ERR_Exception, e1);
                    }

                    if (lexemes != null && lexemes.length > 0) {
                        Vector styles = new Vector();

                        _colorizer.createStyles(styles, lexemes, false);

                        StyleRange[] styleResults = (StyleRange[]) styles.toArray(new StyleRange[] {});

                        // move styles back to actual widget offsets in case of
                        // folding
                        if (extra > 0) {
                            for (int i = 0; i < styleResults.length; i++) {
                                StyleRange range = styleResults[i];
                                range.start -= extra;
                            }
                        }

                        e.styles = styleResults;
                    } else {
                        StyleRange[] styles = new StyleRange[1];
                        styles[0] = new StyleRange(e.lineOffset, e.lineText.length(), textForeground, null);
                        e.styles = styles;
                    }
                }
            };
        }

        // Repaint lines if the user is making changes
        if (_textChangeListener == null) {
            _textChangeListener = new TextChangeListener() {

                public void textChanging(TextChangingEvent event) {
                }

                public void textChanged(TextChangedEvent event) {
                    StyledText text = getViewer().getTextWidget();
                    redrawFrom(text, text.getLineAtOffset(text.getCaretOffset()));
                }

                // Used with complex text change events (tabbing, replacement,
                // etc.)
                public void textSet(TextChangedEvent event) {
                    StyledText text = getViewer().getTextWidget();
                    redrawFrom(text, 0);
                }

                private void redrawFrom(StyledText text, int lno) {
                    if (lno < 0 || lno >= text.getLineCount()) {
                        return;
                    }

                    int height = text.getClientArea().height;
                    int width = text.getClientArea().width + text.getHorizontalPixel();
                    try {
                        text.redraw(0, 0, width, height, true);
                    } catch (Exception e) {
                        // Catch errors in redraw as they may be intermittent and the subsequent redraw will complete
                        // successfully
                    }
                }
            };
        }

        getViewer().getTextWidget().addLineStyleListener(_lineStyleListener);
        getViewer().getTextWidget().getContent().addTextChangeListener(_textChangeListener);
    }

    /**
     * Adds lexeme to the list.
     * @param lexeme - lexeme to add.
     * @param lexemes - lexemes
     */
    private void addLexeme(Lexeme lexeme, LexemeList lexemes) {
        lexemes.add(lexeme);
        return;
    }

    /**
     * Gets lexer.
     * @return
     */
    private ILexer getLexer() {
        return TokenTypes.getLexerFactory().getLexer();
    }

    /**
     * Finds last recognized regexp lexeme.
     * @param lexemes - lexemes to search in.
     * @return last recognized lexeme.
     */
    private Lexeme findLastRecognizedRegexpLexeme(LexemeList lexemes) {
        if (lexemes.size() == 0) {
            return null;
        }

        for (int i = lexemes.size() - 1; i >= 0; i--) {
            Lexeme currentLexeme = lexemes.get(i);
            if (TokenTypes.isRegexpType(currentLexeme.getType())) {
                return currentLexeme;
            }
        }

        return null;
    }

    /**
     * Disposes listeners.
     */
    private void disposeListeners() {
        UnifiedEditorsPlugin.getDefault().getPreferenceStore()
                .removePropertyChangeListener(colorizationPreferencesListener);
        LoggingPlugin.getDefault().getLoggingPreferences().removePreferenceListener(loggingPreferenceListener);
        watcher.removeListener(tailListener);
    }

    /**
     * Creates colorizer.
     */
    private void createColorizer() {
        _colorizer = new LoggingColorizer(getLexer().getTokenList(TokenTypes.LANGUAGE));
    }

    /**
     * Gets autobolding preference. 
     * @return autobolding preference.
     */
    private boolean getAutobolding() {
        return LoggingPlugin.getDefault().getLoggingPreferences().getAutoBolding();
    }

    /**
     * Gets decoration support.
     * @param viewer - viewer.
     * @return decoration support.
     */
    protected void createSourceViewerDecorationSupport(ISourceViewer viewer) {
        if (decorationSupport == null) {
            ISharedTextColors sharedColors = EditorsPlugin.getDefault().getSharedTextColors();

            decorationSupport = new SourceViewerDecorationSupport(viewer, null, null, sharedColors);
            configureSourceViewerDecorationSupport(decorationSupport);
            //decorationSupport.showCursorLine();

            decorationSupport.install(LoggingPlugin.getDefault().getPreferenceStore());
            ;
        }
    }

    /**
     * Configures decoration support.
     * @param support - support to configure.
     */
    protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) {
        support.setCursorLinePainterPreferenceKeys(LoggingPreferences.CURSORLINE_KEY,
                LoggingPreferences.CURSORLINE_COLOR_KEY);
        support.setSymbolicFontName(LoggingPreferences.MAIN_TEXT_FONT_KEY);
    }

    /**
     * @return
     */
    private IDocument getDocument() {
        return document;
    }

    /**
     * Updates data.
     * @param data - data to add.
     * @param append - whether to append, or to replace.
     */
    private void updateData(String data, long globalOffset, long globalLength) {
        //if this tab is not active, we have an unread data
        if (!LogTab.this.equals(logView.getActiveTab()) && getAutobolding()) {
            //setting "unreadDataAvailable" states
            unreadDataAvailable = true;

            //making tab caption bold
            makeTabBold(true);
        }

        Integer topIndex = null;
        Integer bottomIndex = null;

        int horizontalScrollPixel = -1;

        ISelection selection = null;
        if (viewer != null) {
            //saving horizontal scrolling position
            horizontalScrollPixel = viewer.getTextWidget().getHorizontalPixel();

            //saving selection
            viewer.setRedraw(false);
            selection = viewer.getSelection();
            topIndex = viewer.getTopIndex();
            bottomIndex = viewer.getBottomIndex();
        }

        updateDocumentData(data, globalOffset, globalLength);

        if (viewer != null) {

            viewer.setRedraw(true);
            //viewer.setSelection(new TextSelection(0,0), false);
            //            if (selection != null)
            //            {
            //                viewer.setSelection(selection, false);
            //            }

            if (followTail) {
                ((LogSourceViewer) viewer).revealLastLine();
            } else {
                if (topIndex != null) {
                    try {
                        int startOffset = document.getLineOffset(topIndex);
                        int endOffset = document.getLineOffset(bottomIndex) + document.getLineLength(bottomIndex);
                        viewer.revealRange(startOffset, endOffset - startOffset);
                        topIndex = viewer.getTopIndex();
                        bottomIndex = viewer.getBottomIndex();
                    } catch (BadLocationException e) {
                        IdeLog.logError(LoggingPlugin.getDefault(),
                                com.aptana.ide.logging.view.Messages.LogTab_ERR_Exception, e);
                    }
                }
            }

            final int horizontalScrollPixel1 = horizontalScrollPixel;

            //restoring horizontal scrolling position
            viewer.getTextWidget().setHorizontalPixel(horizontalScrollPixel1);
        }
    }

    /**
     * Updates document data.
     * @param data - data.
     * @param globalOffset - global offset.
     * @param globalLength - global length.
     */
    private void updateDocumentData(String data, long globalOffset, long globalLength) {
        try {
            IDocument document = getDocument();

            //applying data
            long lastGlobalPosition = currentGlobalOffset + document.getLength();
            if (globalOffset == 0 && globalLength >= Integer.MAX_VALUE) {
                document.replace(0, document.getLength(), data);
                currentGlobalOffset = globalOffset;
            } else if (globalOffset < currentGlobalOffset) {
                int diff = (int) (currentGlobalOffset - globalOffset);
                //checking for part visibility
                if (diff <= data.length()) {

                    int lengthToReplace = (int) (globalLength - diff);
                    String beginData = data.substring(0, diff);
                    String innrerData = data.substring(diff, data.length());

                    //replacing inner data
                    replaceInnerData(innrerData, currentGlobalOffset, lengthToReplace, document);

                    //adding begin data
                    document.replace(0, diff, beginData);
                    currentGlobalOffset = globalOffset;
                } else {
                    //in other case, replacing the whole document
                    document.replace(0, document.getLength(), data);
                    currentGlobalOffset = globalOffset;
                }
            } else if (globalOffset >= currentGlobalOffset && globalOffset < lastGlobalPosition) {
                //replacing inner data
                replaceInnerData(data, globalOffset, globalLength, document);
            } else if (globalOffset == lastGlobalPosition) {
                //adding data to the end of the document
                document.replace(document.getLength(), 0, data);
            } else {
                //replacing whole data with new data
                document.replace(0, document.getLength(), data);
                currentGlobalOffset = globalOffset;
            }

            //fixing number of lines if needed
            int numberOfLines = document.getNumberOfLines();
            int allowedNumberOfLines = LoggingPlugin.getDefault().getLoggingPreferences().getBacklogLines();
            List<String> topLines = new ArrayList<String>();
            if (numberOfLines > allowedNumberOfLines) {
                int toRemoveLinesNum = numberOfLines - allowedNumberOfLines;
                int offset = document.getLineOffset(toRemoveLinesNum);
                for (int i = 0; i < toRemoveLinesNum; i++) {
                    int lineOffset = document.getLineOffset(i);
                    int lineLength = document.getLineLength(i);
                    topLines.add(document.get(lineOffset, lineLength));
                }
                currentGlobalOffset += offset;
                document.replace(0, offset, ""); //$NON-NLS-1$
            }

            if (lexememanager != null) {
                lexememanager.dataAvailable(topLines);
            }
        } catch (Throwable e) {
            IdeLog.logError(LoggingPlugin.getDefault(), com.aptana.ide.logging.view.Messages.LogTab_8, e);
        }
    }

    /**
     * Replaces data, when change starts inside the document.
     * @param data - data to replace with.
     * @param globalOffset - global offset.
     * @param globalLength - global length.
     * @param document - codument.
     * @throws BadLocationException
     */
    private void replaceInnerData(String data, long globalOffset, long globalLength, IDocument document)
            throws BadLocationException {
        int diff = (int) (globalOffset - currentGlobalOffset);
        int lengthToReplace = (int) globalLength;
        if (lengthToReplace == Integer.MAX_VALUE || diff + lengthToReplace > document.getLength()) {
            lengthToReplace = document.getLength() - diff;
        }
        document.replace(diff, lengthToReplace, data);
    }
}