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

org.graalvm.tools.lsp.server.utils.SourceUtils Maven / Gradle / Ivy

/*
 * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package org.graalvm.tools.lsp.server.utils;

import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;

import org.graalvm.options.OptionValues;
import org.graalvm.tools.lsp.server.types.Position;
import org.graalvm.tools.lsp.server.types.Range;
import org.graalvm.tools.lsp.server.types.TextDocumentContentChangeEvent;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter.SourcePredicate;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.api.nodes.LanguageInfo;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;

public final class SourceUtils {

    private static final InteropLibrary INTEROP = InteropLibrary.getFactory().getUncached();

    private SourceUtils() {
        assert false;
    }

    public static final class SourceFix {
        public final String text;
        public final String removedCharacters;
        public final int characterIdx;

        public SourceFix(String text, String removedChracters, int characterIdx) {
            this.text = text;
            this.removedCharacters = removedChracters;
            this.characterIdx = characterIdx;
        }
    }

    public static boolean isLineValid(int zeroBasedLine, Source source) {
        // Source line is one-based
        return zeroBasedLine >= 0 &&
                        (zeroBasedLine < source.getLineCount() ||
                                        (zeroBasedLine == source.getLineCount() && endsWithNewline(source)) ||
                                        (zeroBasedLine == 0 && source.getLineCount() == 0));
    }

    public static boolean isColumnValid(int line, int column, Source source) {
        return column <= source.getLineLength(zeroBasedLineToOneBasedLine(line, source));
    }

    public static int zeroBasedLineToOneBasedLine(int line, Source source) {
        int lc = source.getLineCount();
        if (lc <= line) {
            return Math.max(1, lc);
        }

        return line + 1;
    }

    public static int zeroBasedColumnToOneBasedColumn(int zeroBasedLine, int oneBasedLine, int zeroBasedColumn, Source source) {
        if (zeroBasedLine >= oneBasedLine) { // line overflow
            return source.getLineLength(oneBasedLine) + 1;
        }
        int lc = source.getLineLength(oneBasedLine);
        if (lc < zeroBasedColumn && zeroBasedColumn > 0) {
            return Math.max(1, lc);
        }

        return zeroBasedColumn + 1;
    }

    private static boolean endsWithNewline(Source source) {
        String text = source.getCharacters().toString();
        boolean isNewlineEnd = !text.isEmpty() && text.charAt(text.length() - 1) == '\n';
        return isNewlineEnd;
    }

    public static Range sourceSectionToRange(SourceSection section) {
        if (section == null) {
            return Range.create(0, 0, 0, 0); // TODO: LSP4J removal
        }
        int endColumn = section.getEndColumn();
        if (section.getCharacters().toString().endsWith("\n")) {
            // TODO(ds) Python problem - without correction, goto definition highlighting is not
            // working
            endColumn -= 1;
        }
        return Range.create(section.getStartLine() - 1, section.getStartColumn() - 1, section.getEndLine() - 1, endColumn);
    }

    public static SourceSection findSourceLocation(TruffleInstrument.Env env, Object object, LanguageInfo defaultLanguageInfo) {
        LanguageInfo languageInfo;
        if (INTEROP.hasLanguage(object)) {
            try {
                languageInfo = env.getLanguageInfo(INTEROP.getLanguage(object));
            } catch (UnsupportedMessageException e) {
                CompilerDirectives.transferToInterpreter();
                throw new AssertionError(e);
            }
        } else {
            languageInfo = defaultLanguageInfo;
        }
        Object view = env.getLanguageView(languageInfo, object);
        if (INTEROP.hasSourceLocation(view)) {
            try {
                return INTEROP.getSourceLocation(view);
            } catch (UnsupportedMessageException e) {
                CompilerDirectives.transferToInterpreter();
                throw new AssertionError(e);
            }
        }
        return null;
    }

    public static SourceFix removeLastTextInsertion(TextDocumentSurrogate surrogate, int originalCharacter, TruffleLogger logger) {
        TextDocumentContentChangeEvent lastChange = surrogate.getLastChange();
        if (lastChange == null) {
            return null;
        }
        Range range = lastChange.getRange();
        TextDocumentContentChangeEvent replacementEvent = TextDocumentContentChangeEvent.create("") //
                        .setRange(Range.create(range.getStart(), Position.create(range.getEnd().getLine(), range.getEnd().getCharacter() + lastChange.getText().length()))) //
                        .setRangeLength(lastChange.getText().length());
        String codeBeforeLastChange = applyTextDocumentChanges(Arrays.asList(replacementEvent), surrogate.getSource(), surrogate, logger);
        int characterIdx = originalCharacter - (originalCharacter - range.getStart().getCharacter());

        return new SourceFix(codeBeforeLastChange, lastChange.getText(), characterIdx);
    }

    public static String applyTextDocumentChanges(List list, Source source, TextDocumentSurrogate surrogate, TruffleLogger logger) {
        Source currentSource = null;
        String text = source.getCharacters().toString();
        StringBuilder sb = new StringBuilder(text);
        for (TextDocumentContentChangeEvent event : list) {
            if (currentSource == null) {
                currentSource = source;
            } else {
                currentSource = Source.newBuilder(currentSource.getLanguage(), sb, currentSource.getName()).cached(false).build();
            }

            Range range = event.getRange();
            if (range == null) {
                // The whole file has changed
                sb.setLength(0); // Clear StringBuilder
                sb.append(event.getText());
                continue;
            }

            Position start = range.getStart();
            Position end = range.getEnd();
            int startLine = start.getLine() + 1;
            int endLine = end.getLine() + 1;
            int replaceBegin = currentSource.getLineStartOffset(startLine) + start.getCharacter();
            int replaceEnd = currentSource.getLineStartOffset(endLine) + end.getCharacter();

            sb.replace(replaceBegin, replaceEnd, event.getText());

            if (surrogate != null && surrogate.hasCoverageData()) {
                updateCoverageData(surrogate, currentSource, event.getText(), range, replaceBegin, replaceEnd, logger);
            }
        }
        return sb.toString();
    }

    private static void updateCoverageData(TextDocumentSurrogate surrogate, Source source, String newText, Range range, int replaceBegin, int replaceEnd, TruffleLogger logger) {
        Source newSourceSnippet = Source.newBuilder("dummyLanguage", newText, "dummyCoverage").cached(false).build();
        int linesNewText = newSourceSnippet.getLineCount() + (newText.endsWith("\n") ? 1 : 0) + (newText.isEmpty() ? 1 : 0);

        Source oldSourceSnippet = source.subSource(replaceBegin, replaceEnd - replaceBegin);
        int liensOldText = oldSourceSnippet.getLineCount() + (oldSourceSnippet.getCharacters().toString().endsWith("\n") ? 1 : 0) + (oldSourceSnippet.getLength() == 0 ? 1 : 0);

        int newLineModification = linesNewText - liensOldText;
        logger.log(Level.FINEST, "newLineModification: {0}", newLineModification);

        if (newLineModification != 0) {
            List sections = surrogate.getCoverageLocations();
            sections.stream().filter(section -> section.includes(range)).forEach(section -> {
                SourceSectionReference migratedSection = new SourceSectionReference(section);
                migratedSection.setEndLine(migratedSection.getEndLine() + newLineModification);
                surrogate.replace(section, migratedSection);
                logger.log(Level.FINEST, "Included - Old: {0} Fixed: {1}", new Object[]{section, migratedSection});
            });
            sections.stream().filter(section -> section.behind(range)).forEach(section -> {
                SourceSectionReference migratedSection = new SourceSectionReference(section);
                migratedSection.setStartLine(migratedSection.getStartLine() + newLineModification);
                migratedSection.setEndLine(migratedSection.getEndLine() + newLineModification);
                surrogate.replace(section, migratedSection);
                logger.log(Level.FINEST, "Behind   - Old: {0} Fixed: {1}", new Object[]{section, migratedSection});
            });
        }
    }

    public static Range getRangeFrom(Exception te, InteropLibrary interopLib) {
        Range range = Range.create(0, 0, 0, 0); // TODO: LSP4J removal

        SourceSection sourceLocation;
        try {
            sourceLocation = interopLib.hasSourceLocation(te) ? interopLib.getSourceLocation(te) : null;
        } catch (UnsupportedMessageException um) {
            throw CompilerDirectives.shouldNotReachHere(um);
        }

        if (sourceLocation != null && sourceLocation.isAvailable()) {
            range = sourceSectionToRange(sourceLocation);
        }
        return range;
    }

    public static int convertLineAndColumnToOffset(Source source, int oneBasedLineNumber, int column) {
        int offset = source.getLineStartOffset(oneBasedLineNumber);
        if (column > 0) {
            offset += column - 1;
        }
        return offset;
    }

    public static URI getOrFixFileUri(Source source) {
        if (source.getURI().getScheme().equals("file")) {
            return source.getURI();
        } else if (source.getURI().getScheme().equals("truffle")) {
            // We assume, that the source name is a valid file path if
            // the URI has no file scheme
            Path path = Paths.get(source.getName());
            return path.toUri();
        } else {
            throw new IllegalStateException("Source has an URI with unknown schema: " + source.getURI());
        }
    }

    public static boolean isValidSourceSection(SourceSection sourceSection, OptionValues options) {
        SourcePredicate predicate = SourcePredicateBuilder.newBuilder().excludeInternal(options).build();
        return sourceSection != null && sourceSection.isAvailable() && predicate.test(sourceSection.getSource());
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy