com.palantir.typescript.text.AutoEditStrategy.java Source code

Java tutorial

Introduction

Here is the source code for com.palantir.typescript.text.AutoEditStrategy.java

Source

/*
 * Copyright 2013 Palantir Technologies, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.palantir.typescript.text;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.IAutoEditStrategy;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;

import com.google.common.base.Strings;
import com.palantir.typescript.IPreferenceConstants;
import com.palantir.typescript.TypeScriptPlugin;
import com.palantir.typescript.services.language.EditorOptions;
import com.palantir.typescript.services.language.TextSpan;

/**
 * The auto-edit strategy maintains the proper indentation when inserting line delimiters.
 *
 * @author dcicerone
 */
public final class AutoEditStrategy implements IAutoEditStrategy {

    private static final Pattern INDENTATION = Pattern.compile("^\\s*");
    private static final Pattern JSDOC_MIDDLE = Pattern.compile("\\s*\\* .*");
    private static final Pattern JSDOC_START = Pattern.compile("\\s*/\\*\\*");

    private final TypeScriptEditor editor;
    private final IPreferenceStore preferenceStore;

    private boolean closeBraces;
    private boolean closeJSDocs;
    private int indentSize;
    private boolean spacesForTabs;
    private int tabWidth;

    public AutoEditStrategy(TypeScriptEditor editor, IPreferenceStore preferenceStore) {
        checkNotNull(editor);
        checkNotNull(preferenceStore);

        this.editor = editor;
        this.preferenceStore = preferenceStore;
    }

    @Override
    public void customizeDocumentCommand(IDocument document, DocumentCommand command) {
        try {
            // customize the command if the user hits enter
            if (isLineDelimiter(document, command)) {
                this.readPreferences();

                if (this.closeBrace(document, command)) {
                    return;
                } else if (this.closeJavadoc(document, command)) {
                    return;
                }

                this.adjustIndentation(document, command);
            }
        } catch (BadLocationException e) {
            throw new RuntimeException(e);
        }
    }

    private void adjustIndentation(IDocument document, DocumentCommand command) throws BadLocationException {
        int offset = command.offset;

        // calculate the proper indentation
        int indentation = this.getIndentationAtPosition(offset);
        if (offset > 0 && document.getChar(offset - 1) == '{') {
            indentation += this.indentSize;
        }

        // remove existing whitespace characters at the beginning of the line to put the caret at the beginning of the line
        int line = document.getLineOfOffset(offset);
        String lineText = getLineText(document, line);
        String indentationText = getIndentationText(lineText);
        int lineOffset = document.getLineOffset(line);

        command.text += this.createIndentationText(indentation);
        command.length = Math.max(0, indentationText.length() - (offset - lineOffset));
    }

    private boolean closeBrace(IDocument document, DocumentCommand command) throws BadLocationException {
        int offset = command.offset;

        if (this.closeBraces && offset > 0 && document.getChar(offset - 1) == '{' && !this.isBraceClosed(offset)) {
            int indentation = this.getIndentationAtPosition(offset);
            String caretIndentationText = this.createIndentationText(indentation + this.indentSize);
            String closingBraceIndentationText = this.createIndentationText(indentation);

            command.caretOffset = offset + caretIndentationText.length() + 1;
            command.shiftsCaret = false;
            command.text += caretIndentationText + command.text + closingBraceIndentationText + "}";

            return true;
        }

        return false;
    }

    private boolean closeJavadoc(IDocument document, DocumentCommand command) throws BadLocationException {
        int offset = command.offset;
        int line = document.getLineOfOffset(offset);
        String lineText = getLineText(document, line);
        String indentationText = getIndentationText(lineText);

        if (JSDOC_START.matcher(lineText).matches()) {
            command.text += indentationText + " * ";

            if (this.closeJSDocs && line + 1 < document.getNumberOfLines()) {
                String nextLineText = getLineText(document, line + 1);

                if (!JSDOC_MIDDLE.matcher(nextLineText).matches()) {
                    String defaultLineDelimiter = TextUtilities.getDefaultLineDelimiter(document);

                    command.caretOffset = offset + command.text.length();
                    command.shiftsCaret = false;
                    command.text += defaultLineDelimiter + indentationText + " */";
                }
            }

            return true;
        } else if (JSDOC_MIDDLE.matcher(lineText).matches()) {
            command.text += indentationText + "* ";

            return true;
        }

        return false;
    }

    private String createIndentationText(int indentation) {
        checkArgument(indentation >= 0);

        int tabs = 0;
        int spaces = 0;

        if (this.spacesForTabs) {
            spaces = indentation;
        } else {
            tabs = indentation / this.tabWidth;
            spaces = indentation % this.tabWidth;
        }

        return Strings.repeat("\t", tabs) + Strings.repeat(" ", spaces);
    }

    private int getIndentationAtPosition(int position) {
        EditorOptions options = new EditorOptions(this.indentSize, this.tabWidth, this.spacesForTabs);

        try {
            return this.editor.getLanguageService().getIndentationAtPosition(position, options);
        } catch (RuntimeException e) {
            Status status = new Status(IStatus.ERROR, TypeScriptPlugin.ID, e.getMessage(), e);

            // log the exception
            TypeScriptPlugin.getDefault().getLog().log(status);

            // fallback to no indentation (its better than the enter key not working)
            return 0;
        }
    }

    private boolean isBraceClosed(int offset) {
        List<TextSpan> braceMatching = this.editor.getLanguageService().getBraceMatchingAtPosition(offset - 1);

        // matching braces come in pairs so if there is no pair, there is no match (happens at the end of files)
        if (braceMatching.size() != 2) {
            return false;
        }

        // get the indentation of the opening brace
        int openingBraceIndentation = this.getIndentationAtPosition(offset);

        // get the indentation of the closing brace
        int closingBraceOffset = braceMatching.get(1).getStart();
        IDocument document = this.editor.getDocument();
        try {
            int closingBraceLine = document.getLineOfOffset(closingBraceOffset);
            int closingBraceLineOffset = document.getLineOffset(closingBraceLine);
            int closingBraceIndentation = closingBraceOffset - closingBraceLineOffset;

            // consider the opening brace closed if the opening and closing braces have the same indentation
            return openingBraceIndentation == closingBraceIndentation;
        } catch (BadLocationException e) {
            throw new RuntimeException(e);
        }
    }

    private void readPreferences() {
        this.closeBraces = this.preferenceStore.getBoolean(IPreferenceConstants.EDITOR_CLOSE_BRACES);
        this.closeJSDocs = this.preferenceStore.getBoolean(IPreferenceConstants.EDITOR_CLOSE_JSDOCS);
        this.indentSize = this.preferenceStore.getInt(IPreferenceConstants.EDITOR_INDENT_SIZE);
        this.spacesForTabs = this.preferenceStore
                .getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SPACES_FOR_TABS);
        this.tabWidth = this.preferenceStore
                .getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH);
    }

    private static String getIndentationText(String lineText) {
        Matcher matcher = INDENTATION.matcher(lineText);

        if (matcher.find()) {
            return matcher.group();
        }

        return "";
    }

    private static String getLineText(IDocument document, int line) throws BadLocationException {
        int lineOffset = document.getLineOffset(line);
        String lineDelimiter = document.getLineDelimiter(line);
        int lineLengthWithoutDelimiter = document.getLineLength(line)
                - (lineDelimiter != null ? lineDelimiter.length() : 0);

        return document.get(lineOffset, lineLengthWithoutDelimiter);
    }

    private static boolean isLineDelimiter(IDocument document, DocumentCommand command) {
        String[] legalLineDelimiters = document.getLegalLineDelimiters();

        return command.length == 0 && command.text != null
                && TextUtilities.equals(legalLineDelimiters, command.text) != -1;
    }
}