@@ -253,55 +253,31 @@ trait MessageRendering {
253253 else
254254 pos
255255
256- /** Render diagnostics with positions in different files separately */
257- private def renderSeparateSpans (dia : Diagnostic )(using Context ): String =
256+ /** Render a message using multi-span information from Message.parts. */
257+ def messageAndPosFromParts (dia : Diagnostic )(using Context ): String =
258258 val msg = dia.msg
259259 val pos = dia.pos
260260 val pos1 = adjust(pos.nonInlined)
261- given Level = Level (dia.level)
262- given Offset =
263- val maxLineNumber = if pos.exists then pos1.endLine + 1 else 0
264- Offset (maxLineNumber.toString.length + 2 )
265-
266- val sb = StringBuilder ()
267- val posString = posStr(pos1, msg, diagnosticLevel(dia))
268- if posString.nonEmpty then sb.append(posString).append(EOL )
261+ val msgParts = msg.parts
269262
270- if pos.exists && pos1.exists && pos1.source.file.exists then
271- val (srcBefore, srcAfter, offset) = sourceLines(pos1)
272- val marker = positionMarker(pos1)
273- val err = errorMsg(pos1, msg.message)
274- sb.append((srcBefore ::: marker :: err :: srcAfter).mkString(EOL ))
275- else
276- sb.append(msg.message)
263+ if msgParts.isEmpty then
264+ return msg.leading.getOrElse(" " ) + (if msg.leading.isDefined then " \n " else " " ) + msg.message
277265
278- dia.getSubdiags.foreach(addSubdiagnostic(sb, _))
279- sb.toString
266+ // Collect all positions from message parts
267+ val validParts = msgParts.filter(_.srcPos.exists)
280268
281- def messageAndPosMultiSpan (dia : Diagnostic )(using Context ): String =
282- val msg = dia.msg
283- val pos = dia.pos
284- val pos1 = adjust(pos.nonInlined)
285- val subdiags = dia.getSubdiags
286-
287- // Collect all positions with their associated messages
288- case class PosAndMsg (pos : SourcePosition , msg : Message , isPrimary : Boolean )
289- val allPosAndMsg = PosAndMsg (pos1, msg, true ) :: subdiags.map(s => PosAndMsg (adjust(s.pos), s.msg, false ))
290- val validPosAndMsg = allPosAndMsg.filter(_.pos.exists)
291-
292- if validPosAndMsg.isEmpty then
293- return msg.message
269+ if validParts.isEmpty then
270+ return msg.leading.getOrElse(" " ) + (if msg.leading.isDefined then " \n " else " " ) + msg.message
294271
295272 // Check all positions are in the same source file
296- val source = validPosAndMsg.head.pos.source
297- if ! validPosAndMsg.forall(_.pos.source == source) || ! source.file.exists then
298- // Cannot render multi-span if positions are in different files
299- // Fall back to showing them separately
300- return renderSeparateSpans(dia)
273+ val source = validParts.head.srcPos.source
274+ if ! validParts.forall(_.srcPos.source == source) || ! source.file.exists then
275+ // TODO: support rendering source positions across multiple files
276+ return msg.leading.getOrElse(" " ) + (if msg.leading.isDefined then " \n " else " " ) + msg.message
301277
302278 // Find the line range covering all positions
303- val minLine = validPosAndMsg .map(_.pos .startLine).min
304- val maxLine = validPosAndMsg .map(_.pos .endLine).max
279+ val minLine = validParts .map(_.srcPos .startLine).min
280+ val maxLine = validParts .map(_.srcPos .endLine).max
305281 val maxLineNumber = maxLine + 1
306282
307283 given Level = Level (dia.level)
@@ -313,12 +289,13 @@ trait MessageRendering {
313289 val posString = posStr(pos1, msg, diagnosticLevel(dia))
314290 if posString.nonEmpty then sb.append(posString).append(EOL )
315291
316- // Always display primary error message before code snippet
317- sb.append(msg.message)
318- if ! msg.message.endsWith(EOL ) then sb.append(EOL )
292+ // Display leading text if present
293+ msg.leading.foreach { leadingText =>
294+ sb.append(leadingText)
295+ if ! leadingText.endsWith(EOL ) then sb.append(EOL )
296+ }
319297
320298 // Render the unified code snippet
321- // Get syntax-highlighted content for the entire range
322299 val startOffset = source.lineToOffset(minLine)
323300 val endOffset = source.nextLine(source.lineToOffset(maxLine))
324301 val content = source.content.slice(startOffset, endOffset)
@@ -352,19 +329,13 @@ trait MessageRendering {
352329 sb.append(lnum).append(lineContent.stripLineEnd).append(EOL )
353330
354331 // Find all positions that should show markers after this line
355- // A position shows its marker after its start line
356- val positionsOnLine = validPosAndMsg.filter(_.pos.startLine == lineNum)
357- .sortBy(pm => (pm.pos.startColumn, ! pm.isPrimary)) // Primary positions first if same column
358-
359- for posAndMsg <- positionsOnLine do
360- // Use '^' for primary error, '-' for sub-diagnostics
361- val markerChar = if posAndMsg.isPrimary then '^' else '-'
362- val marker = positionMarker(posAndMsg.pos, markerChar)
363- // For primary position: use PrimaryNote if available, otherwise use primary message
364- val messageToShow =
365- if posAndMsg.isPrimary then dia.getPrimaryNote.map(_.message).getOrElse(posAndMsg.msg.message)
366- else posAndMsg.msg.message
367- val err = errorMsg(posAndMsg.pos, messageToShow)
332+ val partsOnLine = validParts.filter(_.srcPos.startLine == lineNum)
333+ .sortBy(p => (p.srcPos.startColumn, ! p.isPrimary))
334+
335+ for part <- partsOnLine do
336+ val markerChar = if part.isPrimary then '^' else '-'
337+ val marker = positionMarker(part.srcPos, markerChar)
338+ val err = errorMsg(part.srcPos, part.text)
368339 sb.append(marker).append(EOL )
369340 sb.append(err).append(EOL )
370341
@@ -381,7 +352,7 @@ trait MessageRendering {
381352 sb.append(EOL ).append(offsetBox).append(" longer explanation available when compiling with `-explain`" )
382353
383354 sb.toString
384- end messageAndPosMultiSpan
355+ end messageAndPosFromParts
385356
386357 /** The whole message rendered from `dia.msg`.
387358 *
@@ -403,9 +374,11 @@ trait MessageRendering {
403374 *
404375 */
405376 def messageAndPos (dia : Diagnostic )(using Context ): String =
406- if dia.getSubdiags.nonEmpty then messageAndPosMultiSpan(dia)
377+ val msg = dia.msg
378+ // Check if message provides its own multi-span structure
379+ if msg.leading.isDefined || msg.parts.nonEmpty then
380+ messageAndPosFromParts(dia)
407381 else
408- val msg = dia.msg
409382 val pos = dia.pos
410383 val pos1 = adjust(pos.nonInlined) // innermost pos contained by call.pos
411384 val outermost = pos.outermost // call.pos
@@ -439,8 +412,6 @@ trait MessageRendering {
439412 end if
440413 else sb.append(msg.message)
441414
442- dia.getSubdiags.foreach(addSubdiagnostic(sb, _))
443-
444415 if dia.isVerbose then
445416 appendFilterHelp(dia, sb)
446417
@@ -458,20 +429,6 @@ trait MessageRendering {
458429 sb.toString
459430 end messageAndPos
460431
461- private def addSubdiagnostic (sb : StringBuilder , subdiag : Subdiagnostic )(using Context , Level , Offset ): Unit =
462- val pos1 = adjust(subdiag.pos)
463- val msg = subdiag.msg
464- assert(pos1.exists && pos1.source.file.exists)
465-
466- val posString = posStr(pos1, msg, " Note" , isSubdiag = true )
467- val (srcBefore, srcAfter, offset) = sourceLines(pos1)
468- val marker = positionMarker(pos1, '-' ) // Use '-' for sub-diagnostics
469- val err = errorMsg(pos1, msg.message)
470-
471- val diagText = (posString :: srcBefore ::: marker :: err :: srcAfter).mkString(EOL )
472- sb.append(EOL )
473- sb.append(diagText)
474-
475432 private def hl (str : String )(using Context , Level ): String =
476433 summon[Level ].value match
477434 case interfaces.Diagnostic .ERROR => Red (str).show
0 commit comments