diff --git a/src/Pure/Thy/latex.scala b/src/Pure/Thy/latex.scala --- a/src/Pure/Thy/latex.scala +++ b/src/Pure/Thy/latex.scala @@ -1,217 +1,221 @@ /* Title: Pure/Thy/latex.scala Author: Makarius Support for LaTeX. */ package isabelle import java.io.{File => JFile} import scala.annotation.tailrec +import scala.collection.mutable import scala.util.matching.Regex object Latex { /* output text and positions */ type Text = XML.Body def output(latex_text: Text, file_pos: String = ""): String = { - def position(a: String, b: String): String = "%:%" + a + "=" + b + "%:%" + var line = 1 + val result = new mutable.ListBuffer[String] + val positions = new mutable.ListBuffer[String] - var positions: List[String] = - if (file_pos.isEmpty) Nil - else List(position(Markup.FILE, file_pos), "\\endinput") + def position(a: String, b: String): String = "%:%" + a + "=" + b + "%:%\n" - var line = 1 - var result = List.empty[String] + if (file_pos.nonEmpty) { + positions += "\\endinput\n" + positions += position(Markup.FILE, file_pos) + } def traverse(body: XML.Body): Unit = { body.foreach { case XML.Wrapped_Elem(_, _, _) => case XML.Elem(markup, body) => if (markup.name == Markup.DOCUMENT_LATEX) { for { l <- Position.Line.unapply(markup.properties) if positions.nonEmpty } { val s = position(Value.Int(line), Value.Int(l)) - if (positions.head != s) positions ::= s + if (positions.last != s) positions += s } traverse(body) } case XML.Text(s) => line += s.count(_ == '\n') - result ::= s + result += s } } traverse(latex_text) - result.reverse.mkString + cat_lines(positions.reverse) + result ++= positions + result.mkString } /* generated .tex file */ private val File_Pattern = """^%:%file=(.+)%:%$""".r private val Line_Pattern = """^*%:%(\d+)=(\d+)%:%$""".r def read_tex_file(tex_file: Path): Tex_File = { val positions = Line.logical_lines(File.read(tex_file)).reverse. takeWhile(_ != "\\endinput").reverse val source_file = positions match { case File_Pattern(file) :: _ => Some(file) case _ => None } val source_lines = if (source_file.isEmpty) Nil else positions.flatMap(line => line match { case Line_Pattern(Value.Int(line), Value.Int(source_line)) => Some(line -> source_line) case _ => None }) new Tex_File(tex_file, source_file, source_lines) } final class Tex_File private[Latex]( tex_file: Path, source_file: Option[String], source_lines: List[(Int, Int)]) { override def toString: String = tex_file.toString def source_position(l: Int): Option[Position.T] = source_file match { case None => None case Some(file) => val source_line = source_lines.iterator.takeWhile({ case (m, _) => m <= l }). foldLeft(0) { case (_, (_, n)) => n } if (source_line == 0) None else Some(Position.Line_File(source_line, file)) } def position(line: Int): Position.T = source_position(line) getOrElse Position.Line_File(line, tex_file.implode) } /* latex log */ def latex_errors(dir: Path, root_name: String): List[String] = { val root_log_path = dir + Path.explode(root_name).ext("log") if (root_log_path.is_file) { for { (msg, pos) <- filter_errors(dir, File.read(root_log_path)) } yield "Latex error" + Position.here(pos) + ":\n" + Library.indent_lines(2, msg) } else Nil } def filter_errors(dir: Path, root_log: String): List[(String, Position.T)] = { val seen_files = Synchronized(Map.empty[JFile, Tex_File]) def check_tex_file(path: Path): Option[Tex_File] = seen_files.change_result(seen => seen.get(path.file) match { case None => if (path.is_file) { val tex_file = read_tex_file(path) (Some(tex_file), seen + (path.file -> tex_file)) } else (None, seen) case some => (some, seen) }) def tex_file_position(path: Path, line: Int): Position.T = check_tex_file(path) match { case Some(tex_file) => tex_file.position(line) case None => Position.Line_File(line, path.implode) } object File_Line_Error { val Pattern: Regex = """^(.*?\.\w\w\w):(\d+): (.*)$""".r def unapply(line: String): Option[(Path, Int, String)] = line match { case Pattern(file, Value.Int(line), message) => val path = File.standard_path(file) if (Path.is_wellformed(path)) { val file = (dir + Path.explode(path)).canonical val msg = Library.perhaps_unprefix("LaTeX Error: ", message) if (file.is_file) Some((file, line, msg)) else None } else None case _ => None } } val Line_Error = """^l\.\d+ (.*)$""".r val More_Error = List( "", "