All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.smithy.lsp.ext.Document Maven / Gradle / Ivy

There is a newer version: 0.4.1
Show newest version
/*
 * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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 software.amazon.smithy.lsp.ext;

import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;

public final class Document {

    public static Position blankPosition = new Position(-1, 0);
    public static Position startPosition = new Position(0, 0);

    private Document() {
    }

    /**
     * Identify positions of all parts of document preamble.
     *
     * @param lines lines of the source file
     * @return document preamble
     */
    public static DocumentPreamble detectPreamble(List lines) {
        Range namespaceRange = new Range(blankPosition, blankPosition);
        Range useBlockRange = new Range(blankPosition, blankPosition);
        Set imports = new HashSet<>();
        int firstUseStatementLine = 0;
        String firstUseStatement = "";
        int lastUseStatementLine = 0;
        String lastUseStatement = "";
        boolean collectUseBlock = true;
        boolean collectNamespace = true;
        int endOfPreamble = 0;
        Optional currentNamespace = Optional.empty();
        Optional idlVersion = Optional.empty();
        Optional operationInputSuffix = Optional.empty();
        Optional operationOutputSuffix = Optional.empty();
        for (int i = 0; i < lines.size(); i++) {
            String line = lines.get(i).trim();
            if (line.startsWith("namespace ") && collectNamespace) {
                currentNamespace = Optional.of(line.substring(10));
                namespaceRange = getNamespaceRange(i, lines.get(i));
                collectNamespace = false;
            } else if (line.startsWith("use ") && collectUseBlock) {
                imports.add(getImport(line));
                if (firstUseStatement.isEmpty()) {
                    firstUseStatementLine = i;
                    firstUseStatement = lines.get(i);
                }
                if (i > lastUseStatementLine || lastUseStatement.isEmpty()) {
                    lastUseStatementLine = i;
                    lastUseStatement = lines.get(i);
                }
            } else if (line.startsWith("$version:")) {
                idlVersion = getControlStatementValue(line, "version");
            } else if (line.startsWith("$operationInputSuffix:")) {
                operationInputSuffix = getControlStatementValue(line, "operationInputSuffix");
            } else if (line.startsWith("$operationOutputSuffix:")) {
                operationOutputSuffix = getControlStatementValue(line, "operationOutputSuffix");
            } else if (line.startsWith("//") || line.isEmpty() || line.startsWith("metadata")) {
                // Skip docs, empty lines and single-line metadata.
            } else if (collectNamespace) {
                // While the namespace has not been collected, skip any lines related to the metadata section.
            } else {
                // Stop collecting use statements.
                collectUseBlock = false;
                if (endOfPreamble == 0) {
                    endOfPreamble = i - 1;
                }
            }
        }

        if (!firstUseStatement.isEmpty()) {
            useBlockRange = getUseBlockRange(firstUseStatementLine, firstUseStatement, lastUseStatementLine,
                    lastUseStatement);
        }

        boolean blankSeparated = lines.get(endOfPreamble).trim().isEmpty();

        return new DocumentPreamble(currentNamespace, namespaceRange, idlVersion, operationInputSuffix,
                operationOutputSuffix, useBlockRange, imports, blankSeparated);
    }

    // Strip control statement key, trim whitespace and then remove quotes.
    private static Optional getControlStatementValue(String line, String key) {
        String quotedValue = line.substring(key.length() + 2).trim();
        return Optional.of(quotedValue.substring(1, quotedValue.length() - 1));
    }

    private static String getImport(String useStatement) {
        return useStatement.trim().split("use ", 2)[1].trim();
    }

    private static Range getUseBlockRange(int startLine, String startLineStatement,
                                          int endLine, String endLineStatement) {
        return new Range(getStartPosition(startLine, startLineStatement), new Position(endLine,
                endLineStatement.length()));
    }

    private static Range getNamespaceRange(int lineNumber, String content) {
        return new Range(getStartPosition(lineNumber, content), new Position(lineNumber, content.length()));
    }

    private static Position getStartPosition(int lineNumber, String content) {
        return new Position(lineNumber, getStartOffset(content));
    }

    private static int getStartOffset(String line) {
        int offset = 0;
        while (line.charAt(offset) == ' ') {
            offset++;
        }
        return offset;
    }

    /**
     * Constructs a text edit that inserts a statement (usually `use ...`) in the correct place
     * in the preamble.
     *
     * @param line     text to insert
     * @param preamble document preamble
     * @return a text edit
     */
    public static TextEdit insertPreambleLine(String line, DocumentPreamble preamble) {
        String trailingNewLine;
        if (!preamble.isBlankSeparated()) {
            trailingNewLine = "\n";
        } else {
            trailingNewLine = "";
        }
        // case 1 - there's no use block at all, so we need to insert the line directly
        // under namespace
        if (preamble.getUseBlockRange().getStart() == Document.blankPosition) {
            // case 1.a - there's no namespace - that means the document is invalid
            // so we'll just insert the line at the beginning of the document
            if (preamble.getNamespaceRange().getStart() == Document.blankPosition) {
                return new TextEdit(new Range(Document.startPosition, Document.startPosition), line + trailingNewLine);
            } else {
                Position namespaceEnd = preamble.getNamespaceRange().getEnd();
                namespaceEnd.setCharacter(namespaceEnd.getCharacter() + 1);
                return new TextEdit(new Range(namespaceEnd, namespaceEnd), "\n" + line + trailingNewLine);
            }
        } else {
            Position useBlockEnd = preamble.getUseBlockRange().getEnd();
            return new TextEdit(new Range(useBlockEnd, useBlockEnd), "\n" + line + trailingNewLine);
        }
    }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy