From e69e6a1eeb71e46f5e826873072ea0e0615b23e6 Mon Sep 17 00:00:00 2001 From: Ivan Cilcic Date: Thu, 8 Aug 2019 15:26:30 +0300 Subject: [PATCH] Create simple text annotator based on binary search Co-authored-by: Simon Ogorodnik --- .../kotlin/compiler/visualizer/Annotator.kt | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 compiler/visualizer/common/src/org/jetbrains/kotlin/compiler/visualizer/Annotator.kt diff --git a/compiler/visualizer/common/src/org/jetbrains/kotlin/compiler/visualizer/Annotator.kt b/compiler/visualizer/common/src/org/jetbrains/kotlin/compiler/visualizer/Annotator.kt new file mode 100644 index 00000000000..92a015a7cd9 --- /dev/null +++ b/compiler/visualizer/common/src/org/jetbrains/kotlin/compiler/visualizer/Annotator.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.compiler.visualizer + +import com.intellij.openapi.util.TextRange + +object Annotator { + class AnnotationInfo(val text: String, val globalRange: TextRange) + + class RangeSet { + data class Rec(val value: T, val rangeInLine: TextRange) + val localRanges = ArrayList>() + + private inline fun binarySearch(range: TextRange, found: (Int, Rec?) -> U): U { + var high = localRanges.size - 1 + var low = 0 + var median = 0 + println("search: $low, $high, ins: $range") + while (low <= high) { + median = (high + low) / 2 + val element = localRanges[median] + + println("cmp ${element.rangeInLine}") + when { + range.endOffset < element.rangeInLine.startOffset -> { + println("take low, $low, $high") + high = median - 1 + } + range.startOffset > element.rangeInLine.endOffset -> { + println("take high, $low, $high") + low = median + 1 + } + else -> { + println("conflict $median") + return found(median, element) + } + } + } + println("empty at $median") + return found(median, null) + } + + fun put(range: TextRange, element: T): Boolean { + binarySearch(range) { index, conflicting -> + return if (conflicting == null) { + localRanges.add(index, Rec(element, range)) + true + } else { + false + } + } + } + } + + private fun tryPutToAnnotationLines(annotationLines: MutableList>, annotation: Pair) { + val range = annotation.second + fun buildArrow(from: RangeSet) { + for (line in annotationLines) { + if (line == from) { + break + } + line.put(TextRange(range.startOffset, range.startOffset + 1), "|") + } + } + + for (line in annotationLines.drop(1)) { + if (line.put(range, annotation.first.text)) { + buildArrow(line) + return + } + } + annotationLines.add(RangeSet()) + tryPutToAnnotationLines(annotationLines, annotation) + } + + fun annotate(text: String, annotation: List): List { + val lines = text.lines() + val resultLines = mutableListOf() + val annotationLines = mutableListOf>() + var lineStartOffset = 0 + for (line in lines) { + annotationLines.clear() + val lineEndOffset = lineStartOffset + line.length + val annotations = + annotation.filter { it.globalRange.startOffset in lineStartOffset until lineEndOffset } + .sortedByDescending { it.globalRange.startOffset } + .map { + val startInLine = it.globalRange.startOffset - lineStartOffset + it to TextRange(startInLine, startInLine + it.text.length) + } + for (ann in annotations) { + tryPutToAnnotationLines(annotationLines, ann) + } + annotationLines.asReversed().mapTo(resultLines) { + var prevOffset = 2 + println(it.localRanges) + it.localRanges.fold("//") { acc, element -> + var res = acc + res += " ".repeat(element.rangeInLine.startOffset - prevOffset) + prevOffset = element.rangeInLine.endOffset + res += element.value + res + } + } + resultLines.add(line) + + lineStartOffset = lineEndOffset + 1 + } + return resultLines + } +} \ No newline at end of file