@@ -32,6 +32,7 @@ object ScipPrinters {
3232 doc.occurrencesList.groupBy { it.getRange(0 ) }
3333 val symtab: Map <String , SymbolInformation > =
3434 doc.symbolsList.associateBy { it.symbol }
35+ val input = SourceInput (text)
3536
3637 val syntheticDefinitions: Map <String , List <SymbolInformation >> =
3738 doc.symbolsList
@@ -68,10 +69,10 @@ object ScipPrinters {
6869 val occurrences =
6970 (occurrencesByLine[i] ? : emptyList()).sortedWith(occurrenceOrdering)
7071 for (occ in occurrences) {
71- formatOccurrence(out , occ, line, symtab, null )
72+ formatOccurrence(input, out , occ, line, symtab, null )
7273 if ((occ.symbolRoles and SymbolRole .Definition_VALUE ) > 0 ) {
7374 syntheticDefinitions[occ.symbol]?.forEach { syntheticDefinition ->
74- formatOccurrence(out , occ, line, symtab, syntheticDefinition)
75+ formatOccurrence(input, out , occ, line, symtab, syntheticDefinition)
7576 }
7677 }
7778 }
@@ -120,21 +121,57 @@ object ScipPrinters {
120121 val endColumn : Int ,
121122 )
122123
123- private fun positionOf (occ : Occurrence ): OccurrencePos =
124+ /* *
125+ * Faithful port of `moped.reporters.Position.range` + `RangePosition`:
126+ * the SCIP `(line, column)` pair is converted to a flat character offset
127+ * and back. The round-trip matters for occurrences whose end column
128+ * overflows its start line (e.g. Kotlin `companion object` definitions),
129+ * where the overflow "carries" the end onto a later line, turning a raw
130+ * single-line range into a rendered multi-line range.
131+ */
132+ private fun positionOf (input : SourceInput , occ : Occurrence ): OccurrencePos {
133+ val rawStartLine: Int
134+ val rawStartColumn: Int
135+ val rawEndLine: Int
136+ val rawEndColumn: Int
124137 when (occ.rangeCount) {
125- 3 -> OccurrencePos (occ.getRange(0 ), occ.getRange(1 ), occ.getRange(0 ), occ.getRange(2 ))
126- 4 -> OccurrencePos (occ.getRange(0 ), occ.getRange(1 ), occ.getRange(2 ), occ.getRange(3 ))
138+ 3 -> {
139+ rawStartLine = occ.getRange(0 )
140+ rawStartColumn = occ.getRange(1 )
141+ rawEndLine = occ.getRange(0 )
142+ rawEndColumn = occ.getRange(2 )
143+ }
144+ 4 -> {
145+ rawStartLine = occ.getRange(0 )
146+ rawStartColumn = occ.getRange(1 )
147+ rawEndLine = occ.getRange(2 )
148+ rawEndColumn = occ.getRange(3 )
149+ }
127150 else -> throw IllegalArgumentException (" Invalid range: $occ " )
128151 }
152+ // moped's Position.range returns NoPosition (all -1) for empty input.
153+ if (input.isEmpty) return OccurrencePos (- 1 , - 1 , - 1 , - 1 )
154+ val start = input.lineToOffset(rawStartLine) + rawStartColumn
155+ val end = input.lineToOffset(rawEndLine) + rawEndColumn
156+ val startLine = input.offsetToLine(start)
157+ val endLine = input.offsetToLine(end)
158+ return OccurrencePos (
159+ startLine = startLine,
160+ startColumn = start - input.lineToOffset(startLine),
161+ endLine = endLine,
162+ endColumn = end - input.lineToOffset(endLine),
163+ )
164+ }
129165
130166 private fun formatOccurrence (
167+ input : SourceInput ,
131168 out : StringBuilder ,
132169 occ : Occurrence ,
133170 line : String ,
134171 symtab : Map <String , SymbolInformation >,
135172 syntheticDefinition : SymbolInformation ? ,
136173 ) {
137- val pos = positionOf(occ)
174+ val pos = positionOf(input, occ)
138175 val isMultiline = pos.startLine != pos.endLine
139176 val width =
140177 if (isMultiline) line.length - pos.startColumn - 1
@@ -239,4 +276,57 @@ object ScipPrinters {
239276 if (start < text.length) result + = text.substring(start)
240277 return result
241278 }
279+
280+ /* *
281+ * Port of `moped.reporters.Input`'s offset/line bookkeeping. Only the
282+ * pieces needed by [positionOf] are reproduced ([lineToOffset],
283+ * [offsetToLine], [isEmpty]).
284+ */
285+ private class SourceInput (text : String ) {
286+ private val chars: CharArray = text.toCharArray()
287+
288+ val isEmpty: Boolean = text.isEmpty()
289+
290+ // Offset of the first character of each line; a trailing sentinel
291+ // (== chars.size) is appended when the text does not end in '\n'.
292+ private val lineIndices: IntArray = run {
293+ val buf = ArrayList <Int >()
294+ buf.add(0 )
295+ var i = 0
296+ while (i < chars.size) {
297+ if (chars[i] == ' \n ' ) buf.add(i + 1 )
298+ i++
299+ }
300+ if (buf[buf.size - 1 ] != chars.size) buf.add(chars.size)
301+ buf.toIntArray()
302+ }
303+
304+ fun lineToOffset (line : Int ): Int {
305+ require(line in 0 .. (lineIndices.size - 1 )) {
306+ " $line is not a valid line number, allowed [0..${lineIndices.size - 1 } ]"
307+ }
308+ return lineIndices[line]
309+ }
310+
311+ fun offsetToLine (offset : Int ): Int {
312+ require(offset in 0 .. chars.size) {
313+ " $offset is not a valid offset, allowed [0..${chars.size} ]"
314+ }
315+ // File ending in '\n': an offset at EOF is last_line+1:0.
316+ if (offset == chars.size && chars.isNotEmpty() && chars[offset - 1 ] == ' \n ' ) {
317+ return lineIndices.size - 1
318+ }
319+ var lo = 0
320+ var hi = lineIndices.size - 1
321+ while (hi - lo > 1 ) {
322+ val mid = (hi + lo) / 2
323+ when {
324+ offset < lineIndices[mid] -> hi = mid
325+ lineIndices[mid] == offset -> return mid
326+ else -> lo = mid
327+ }
328+ }
329+ return lo
330+ }
331+ }
242332}
0 commit comments