IDEAのterminal(sbt pluginではない)、からsbtを起動してcompileなどすると、errorやwarning出た場合にそのpathがclick可能で、そうすると該当のsource codeに飛べて便利だったのだけど、いつの間にか不可能になってるんだけど、これどこかに設定あるのか、version上がって何故か不可能になったのか
— Kenji Yoshida (@xuwei_k) 2024年1月31日
記憶違いなのか、IDEAのversionか設定で変わって出来なくなったのか、本当に謎なんですが、大量の警告やエラー直す場合に、これの有無でけっこう生産性が変わるので。
「sbt pluginではない」というのは、IDEA関係ない普通のsbt pluginとしてではないし、IDEA自体のsbtやScala pluginも関係ない、という話です。 「IDEA自体のsbtやScala plugin」は存在して、そこからもsbtのshellは立ち上がり、それ経由だと昔から勝手にクリック可能なのですが、そちらはtab補完が壊滅的になるので使いたくない、という事情があります。 Scalaとはおそらく全く関係ないIDEAに標準搭載されてる(よくわかってない?)terminalだと、sbtのshellのtab補完その他がそのままいい感じに動きます。
こちらがsbt shell(pathがクリック可能だけどtab補完死ぬので使いたくない方)
こちらがterminal(tab補完いい感じになるけど、デフォルトでは?pathがクリックできない方)
解決策ですが、普通のerrorやwarnの場合は、以下のようにbuild.sbtに書いて無理やり file:///
付与すれば、リンクになっていけました。
(動作確認したsbtは1.9.9)
ただし、これScala 3だとダメですね・・・(調査中 => 下に別の方法追記した)
build.sbt
sourcePositionMappers += { (p: xsbti.Position) => Option { new xsbti.Position { override def line() = p.line() override def lineContent() = p.lineContent() override def offset() = p.offset() override def pointer() = p.pointer() override def pointerSpace() = p.pointerSpace() override def sourcePath() = p.sourcePath().map(a => "file:///" + a) override def sourceFile() = p.sourceFile() } } } // 以下の2つはこれのテストのためで、場合による scalaVersion := "2.13.13" scalacOptions += "-deprecation"
わざと警告出すための適当なファイル
class A { def x = Stream(2) }
before
after
— Kenji Yoshida (@xuwei_k) 2024年3月4日
また、sbt-scalafixは、ほぼ同じような形式で出すのに、独自に出しているので、上記では変わらないのですが、これも以下のように若干無理やり独自にやるといけました
(ただし自分がつい最近追加した機能を使ってるので0.12.0以降が必要)
(これのためにこのkey追加したわけではなく、他にやりたいことあったのがきっかけだが)
ThisBuild / scalafixCallback := { val log = sbt.ConsoleLogger(System.out) import _root_.scalafix.interfaces.ScalafixSeverity { (x: _root_.scalafix.interfaces.ScalafixDiagnostic) => val msg = s"file:///${x.getClass.getMethod("diagnostic").invoke(x)}" x.severity() match { case ScalafixSeverity.INFO => log.info(msg) case ScalafixSeverity.WARNING => log.warn(msg) case ScalafixSeverity.ERROR => log.err(msg) } } }
追記:
Scala 3の場合は、とりあえず以下のようにすれば、2回出てしまうが、とりあえず出ます
// https://github.com/sbt/sbt/blob/48c23761dc5ffe191a79424ba830dafcd2ec7631/main/src/main/scala/sbt/Keys.scala#L639 val compilerReporter = taskKey[xsbti.Reporter]("") Seq(Compile, Test).map { x => x / compile / compilerReporter := { val underlying = (x / compile / compilerReporter).value val logger = streams.value.log if (scalaBinaryVersion.value == "3") { new xsbti.Reporter { override def reset() = underlying.reset() override def hasErrors = underlying.hasErrors override def hasWarnings = underlying.hasWarnings override def printSummary() = underlying.printSummary() override def problems() = underlying.problems() override def log(problem: xsbti.Problem): Unit = { problem.position().sourceFile().ifPresent { f => val x = s"file:///${f.getCanonicalPath}:${problem.position().startLine().orElseGet(() => -1)}" problem.severity() match { case xsbti.Severity.Info => logger.info(x) case xsbti.Severity.Warn => logger.warn(x) case xsbti.Severity.Error => logger.error(x) } } underlying.log(problem) } override def comment(position: xsbti.Position, s: String): Unit = underlying.comment(position, s) } } else { underlying } } }