diff --git a/src/main/scala/de/plugh/asciiprooftree/Main.scala b/src/main/scala/de/plugh/asciiprooftree/Main.scala index 6145a1a..9bc9a8d 100644 --- a/src/main/scala/de/plugh/asciiprooftree/Main.scala +++ b/src/main/scala/de/plugh/asciiprooftree/Main.scala @@ -1,6 +1,6 @@ package de.plugh.asciiprooftree -import de.plugh.asciiprooftree.file.Formatter +import de.plugh.asciiprooftree.file.FileFormatter import org.rogach.scallop.* import java.nio.file.{Files, Path} @@ -17,8 +17,9 @@ class Conf(args: Seq[String]) extends ScallopConf(args): val blockRegex: ScallopOption[String] = opt[String]() val lineRegex: ScallopOption[String] = opt[String]() val useScalaDocstringRegexes: ScallopOption[Boolean] = opt[Boolean]() - val indent: ScallopOption[Int] = opt[Int](default = Some(2)) val noHeuristics: ScallopOption[Boolean] = opt[Boolean]() + val indent: ScallopOption[Int] = opt[Int](default = Some(2)) + val lineOverhang: ScallopOption[Int] = opt[Int](default = Some(0)) verify() @main @@ -29,16 +30,17 @@ def main(args: String*): Unit = if conf.useScalaDocstringRegexes() then (scalaDocstringBlockRe, scalaDocstringLineRe) else (markerBlockRe, markerLineRe) - val formatter = Formatter( + val formatter = FileFormatter( blockRe = conf.blockRegex.map(Regex(_)).getOrElse(defaultBlockRe), lineRe = conf.lineRegex.map(Regex(_)).getOrElse(defaultLineRe), - indent = conf.indent(), heuristics = !conf.noHeuristics(), + indent = conf.indent(), + lineOverhang = conf.lineOverhang(), ) reformat(conf.path(), formatter) -def reformat(path: Path, formatter: Formatter): Unit = +def reformat(path: Path, formatter: FileFormatter): Unit = if Files.isDirectory(path) then val files = Files.list(path).toScala(Seq) for file <- files do reformat(file, formatter) diff --git a/src/main/scala/de/plugh/asciiprooftree/file/Formatter.scala b/src/main/scala/de/plugh/asciiprooftree/file/FileFormatter.scala similarity index 85% rename from src/main/scala/de/plugh/asciiprooftree/file/Formatter.scala rename to src/main/scala/de/plugh/asciiprooftree/file/FileFormatter.scala index 540e90a..12b7a0e 100644 --- a/src/main/scala/de/plugh/asciiprooftree/file/Formatter.scala +++ b/src/main/scala/de/plugh/asciiprooftree/file/FileFormatter.scala @@ -1,15 +1,16 @@ package de.plugh.asciiprooftree.file -import de.plugh.asciiprooftree.tree.Parser +import de.plugh.asciiprooftree.tree.{Parser, ProofTreeFormatter} import scala.collection.mutable import scala.util.boundary import scala.util.matching.Regex import scala.util.matching.Regex.Match -case class Formatter(blockRe: Regex, lineRe: Regex, indent: Int, heuristics: Boolean): +case class FileFormatter(blockRe: Regex, lineRe: Regex, heuristics: Boolean, indent: Int, lineOverhang: Int): private val blockReI = blockRe.pattern.namedGroups().get("block") private val lineReI = lineRe.pattern.namedGroups().get("block") + private val proofTreeFormatter = ProofTreeFormatter(lineOverhang = lineOverhang) private def parseBlockLine(line: String): Option[Block] = boundary: val m = lineRe.findFirstMatchIn(line).getOrElse(boundary.break(None)) @@ -46,7 +47,8 @@ case class Formatter(blockRe: Regex, lineRe: Regex, indent: Int, heuristics: Boo for info <- findBlocks(cleanText) do if resultEnd < info.start then result.append(cleanText.slice(resultEnd, info.start)) - val block = info.block.replace(info.tree.formatted.toString.linesIterator.toIndexedSeq) // Clunky :D + val formattedTree = proofTreeFormatter.formatTree(info.tree) + val block = info.block.replace(formattedTree.shiftAlignLeft.toLines) result.append(block.toLines(indent).mkString("\n")) if info.endsWithNewline then result.append("\n") resultEnd = info.end diff --git a/src/main/scala/de/plugh/asciiprooftree/tree/FormattedProofTree.scala b/src/main/scala/de/plugh/asciiprooftree/tree/FormattedProofTree.scala index ebfe294..21d7225 100644 --- a/src/main/scala/de/plugh/asciiprooftree/tree/FormattedProofTree.scala +++ b/src/main/scala/de/plugh/asciiprooftree/tree/FormattedProofTree.scala @@ -7,11 +7,14 @@ case class FormattedProofTree(lines: Lines, conclusionStart: Int, conclusionEnd: conclusionEnd = conclusionEnd + delta, ) + /** Shift so that the start is always zero. */ + def shiftAlignLeft: FormattedProofTree = shift(-lines.lines.map(_.start).minOption.getOrElse(0)) + def extend(line: Line): FormattedProofTree = copy(lines = lines.extend(line)) def joinHorizontally(right: FormattedProofTree): FormattedProofTree = FormattedProofTree.joinHorizontally(this, right) - override def toString: String = lines.toString + def toLines: IndexedSeq[String] = lines.toLines object FormattedProofTree: val separation = 3 diff --git a/src/main/scala/de/plugh/asciiprooftree/tree/Lines.scala b/src/main/scala/de/plugh/asciiprooftree/tree/Lines.scala index ca98ba3..49ddad1 100644 --- a/src/main/scala/de/plugh/asciiprooftree/tree/Lines.scala +++ b/src/main/scala/de/plugh/asciiprooftree/tree/Lines.scala @@ -9,7 +9,7 @@ case class Lines(lines: IndexedSeq[Line]): def joinHorizontally(right: Lines): (Lines, Int) = Lines.joinHorizontally(this, right) - override def toString: String = lines.reverse.mkString("\n") + def toLines: IndexedSeq[String] = lines.reverse.map(_.toString) object Lines: def empty: Lines = Lines(IndexedSeq()) diff --git a/src/main/scala/de/plugh/asciiprooftree/tree/ProofTree.scala b/src/main/scala/de/plugh/asciiprooftree/tree/ProofTree.scala index dbe6114..b16d91c 100644 --- a/src/main/scala/de/plugh/asciiprooftree/tree/ProofTree.scala +++ b/src/main/scala/de/plugh/asciiprooftree/tree/ProofTree.scala @@ -12,37 +12,6 @@ case class ProofTree(premises: Seq[ProofTree] = Seq(), line: Option[String] = No if this.conclusion.isEmpty then copy(conclusion = Some(conclusion)) else ProofTree().addPremise(this).addConclusion(conclusion) - private def formatLine(start: Int, end: Int, rule: String): Line = - val lineStr = "-" * (end - start) - val lineText = if rule.isEmpty then lineStr else s"$lineStr $rule" - Line(lineText, start) - - def formatted: FormattedProofTree = - val fPremises = premises.map(_.formatted).reduceOption(_.joinHorizontally(_)).getOrElse(FormattedProofTree.empty) - - val lConclusion = conclusion match - case Some(conclusion) => Line(conclusion) - case None => return line match - case Some(rule) => fPremises.extend(formatLine(fPremises.conclusionStart, fPremises.conclusionEnd, rule)) - case None => fPremises - - val aboveMiddle = (fPremises.conclusionStart + fPremises.conclusionEnd) / 2 - val belowMiddle = (lConclusion.start + lConclusion.end) / 2 - - val (aboveCentered, belowCentered) = - if aboveMiddle < belowMiddle then (fPremises.shift(belowMiddle - aboveMiddle), lConclusion) - else if aboveMiddle > belowMiddle then (fPremises, lConclusion.shift(aboveMiddle - belowMiddle)) - else (fPremises, lConclusion) - - val combined = line match - case Some(rule) => - val lineStart = aboveCentered.conclusionStart min belowCentered.start - val lineEnd = aboveCentered.conclusionEnd max belowCentered.end - aboveCentered.extend(formatLine(lineStart, lineEnd, rule)).extend(belowCentered) - case None => aboveCentered.extend(belowCentered) - - combined.copy(conclusionStart = belowCentered.start, conclusionEnd = belowCentered.end) - def containsNoLines: Boolean = line.isEmpty && premises.forall(_.containsNoLines) object ProofTree: diff --git a/src/main/scala/de/plugh/asciiprooftree/tree/ProofTreeFormatter.scala b/src/main/scala/de/plugh/asciiprooftree/tree/ProofTreeFormatter.scala new file mode 100644 index 0000000..57b131d --- /dev/null +++ b/src/main/scala/de/plugh/asciiprooftree/tree/ProofTreeFormatter.scala @@ -0,0 +1,39 @@ +package de.plugh.asciiprooftree.tree + +case class ProofTreeFormatter(lineOverhang: Int = 0): + private def formatLine(start: Int, end: Int, rule: String): Line = + val lineStart = start - lineOverhang + val lineEnd = end + lineOverhang + val lineStr = "-" * (lineEnd - lineStart) + val lineText = if rule.isEmpty then lineStr else s"$lineStr $rule" + Line(lineText, lineStart) + + def formatTree(tree: ProofTree): FormattedProofTree = + val fPremises = tree + .premises + .map(formatTree) + .reduceOption(_.joinHorizontally(_)) + .getOrElse(FormattedProofTree.empty) + + val lConclusion = tree.conclusion match + case Some(conclusion) => Line(conclusion) + case None => return tree.line match + case Some(rule) => fPremises.extend(formatLine(fPremises.conclusionStart, fPremises.conclusionEnd, rule)) + case None => fPremises + + val aboveMiddle = (fPremises.conclusionStart + fPremises.conclusionEnd) / 2 + val belowMiddle = (lConclusion.start + lConclusion.end) / 2 + + val (aboveCentered, belowCentered) = + if aboveMiddle < belowMiddle then (fPremises.shift(belowMiddle - aboveMiddle), lConclusion) + else if aboveMiddle > belowMiddle then (fPremises, lConclusion.shift(aboveMiddle - belowMiddle)) + else (fPremises, lConclusion) + + val combined = tree.line match + case Some(rule) => + val lineStart = aboveCentered.conclusionStart min belowCentered.start + val lineEnd = aboveCentered.conclusionEnd max belowCentered.end + aboveCentered.extend(formatLine(lineStart, lineEnd, rule)).extend(belowCentered) + case None => aboveCentered.extend(belowCentered) + + combined.copy(conclusionStart = belowCentered.start, conclusionEnd = belowCentered.end)