Hi Iain! Iain Lane schrieb: > Hi Christian, > > We recently added tomboy-latex to Debian, and a user has reported a bug > against it[0] which is an upstream issue: > > "With larger notes tomboy (with LaTeX-plugin enabled) becomes really > slow. I can often type whole sentences without any feedback from > tomboy (no cursor movement, no charachters appearing, and tomboys > windows don't redraw if I drag another window over it). When I'm > finished typing and wait for about 30-60 seconds, the typed stuff will > appear all at once. My impression is that there is some O(n^2) > behaviour there, where n is either the total size of the note or the > number of equations in it." > > I checked the source and it does indeed seem to scan over the whole > document whenever the user moves the cursor. This seems to be the > source of the slowdown.
You are right, the plugin is not really optimized with regard to execution time. I have tried to optimize at least some part, please see the attached source code. It would be great if someone could check if it is at least as stable as the previous version. At the moment I'm trying to change the source code so that only a part of the note is scanned for missing images at each text change. Kind regards, Christian
/* * Latex.cs: A tomboy Addin that reders inline LaTeX math code. * * Copyright 2007, Christian ReitwieÃner <[email protected]> * * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ using System; using System.Threading; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Diagnostics; using System.IO; using Mono.Unix; using Gtk; using Tomboy; namespace Tomboy.Latex { public class LatexImageRequest { static Random random = new Random(); static string[] latex_blacklist = {"\\def", "\\let", "\\futurelet", "\\newcommand", "\\renewcomment", "\\else", "\\fi", "\\write", "\\input", "\\include", "\\chardef", "\\catcode", "\\makeatletter", "\\noexpand", "\\toksdef", "\\every", "\\errhelp", "\\errorstopmode", "\\scrollmode", "\\nonstopmode", "\\batchmode", "\\read", "\\csname", "\\newhelp", "\\relax", "\\afterground", "\\afterassignment", "\\expandafter", "\\noexpand", "\\special", "\\command", "\\loop", "\\repeat", "\\toks", "\\output", "\\line", "\\mathcode", "\\name", "\\item", "\\section", "\\mbox", "\\DeclareRobustCommand", "\\[", "\\]"}; string code; LatexAddin requester; public string Code { get { return code; } } public LatexImageRequest (string code, LatexAddin requester) { this.code = code; this.requester = requester; } public void NotifyRequester () { Gtk.Application.Invoke(delegate { requester.ImageGenerated (); } ); } public override bool Equals (System.Object obj) { if (obj == null || GetType() != obj.GetType()) return false; LatexImageRequest r = (LatexImageRequest) obj; return (r.code.Equals(code) && r.requester == requester); } public override int GetHashCode () { return code.GetHashCode() ^ requester.GetHashCode(); } public string CreateImage () { if (code.Length < 5) return null; string realCode = code.Substring(2, code.Length - 4); if (realCode.Trim().Equals(String.Empty)) { return null; } foreach (string s in latex_blacklist) { if (realCode.IndexOf(s) != -1) { return null; } } /* TODO: if there is no output, dvips will not cut the image */ Logger.Log("Latex: Creating image for {0}...", code); string tmpfile = Path.Combine(Path.GetTempPath(), "tbltx_" + random.Next()); try { System.IO.StreamWriter writer = new System.IO.StreamWriter(tmpfile + ".tex", false); writer.Write("\\documentclass[12pt]{article}\\usepackage[dvips]{graphicx}\\usepackage{amsmath}\\usepackage{amssymb}\\pagestyle{empty}"); writer.Write("\\begin{document}\\begin{large}\\begin{gather*}"); writer.Write(realCode); writer.Write("\\end{gather*}\\end{large}\\end{document}"); writer.Close(); Process p = new Process (); p.StartInfo.FileName = "latex"; p.StartInfo.Arguments = "--interaction=nonstopmode \"" + tmpfile + ".tex\""; p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.RedirectStandardError = true; p.StartInfo.WorkingDirectory = Path.GetTempPath(); p.Start (); p.WaitForExit(); if (p.ExitCode != 0) return null; p = new Process (); p.StartInfo.FileName = "dvips"; p.StartInfo.Arguments = "-E -o \"" + tmpfile + ".ps\" \"" + tmpfile + ".dvi\""; p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.RedirectStandardError = true; p.StartInfo.WorkingDirectory = Path.GetTempPath(); p.Start (); p.WaitForExit(); if (p.ExitCode != 0) return null; p = new Process (); p.StartInfo.FileName = "convert"; p.StartInfo.Arguments = "\"" + tmpfile + ".ps\" \"" + tmpfile + ".png\""; p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.RedirectStandardError = true; p.StartInfo.WorkingDirectory = Path.GetTempPath(); p.Start (); p.WaitForExit(); if (p.ExitCode != 0) return null; return tmpfile + ".png"; } finally { string[] extensions = {".tex", ".log", ".aux", ".dvi", ".ps"}; foreach (string ext in extensions) { try { File.Delete(tmpfile + ext); } catch { } } } } } public class LatexManager { IDictionary<string, Gdk.Pixbuf> images; Queue<LatexImageRequest> requests; Thread queue_thread; public LatexManager() { images = new Dictionary<string, Gdk.Pixbuf> (); requests = new Queue<LatexImageRequest> (); queue_thread = new Thread(new ThreadStart(this.WorkOnQueue)); queue_thread.Start(); } public Gdk.Pixbuf GetImage (string code, LatexAddin requester) { lock (queue_thread) { if (images.ContainsKey (code)) { return images[code]; } else { LatexImageRequest req = new LatexImageRequest (code, requester); if (!requests.Contains (req)) { requests.Enqueue (req); Monitor.Pulse(queue_thread); } return null; } } } void WorkOnQueue () { while (true) { LatexImageRequest request; lock (queue_thread) { if (requests.Count == 0) { Monitor.Wait(queue_thread); continue; } request = requests.Dequeue (); } if (images.ContainsKey (request.Code)) { request.NotifyRequester (); } else { string imageFile = request.CreateImage (); if (imageFile == null) { continue; } Gtk.Application.Invoke (delegate { CreatePixbufAndNotify (request, imageFile); }); } } } void CreatePixbufAndNotify (LatexImageRequest request, string imageFile) { try { Gdk.Pixbuf image = new Gdk.Pixbuf (imageFile); lock (queue_thread) { images[request.Code] = image; } request.NotifyRequester (); } finally { File.Delete(imageFile); } } } public class LatexImage { string latex_code; LatexImageTag image_tag; Gtk.TextMark image_position; public Gtk.TextIter ImagePosition { get { return image_position.Buffer.GetIterAtMark(image_position); } } public LatexImage(Gtk.TextIter codeStart, Gtk.TextIter codeEnd, Gdk.Pixbuf image, LatexImageTag image_tag) { latex_code = codeStart.GetText(codeEnd); Gtk.TextBuffer buffer = codeStart.Buffer; Gtk.TextIter imageStart = codeStart; Gtk.TextIter imageEnd; buffer.InsertPixbuf(ref imageStart, image); imageStart.BackwardChar(); image_position = buffer.CreateMark(null, imageStart, false); imageStart = image_position.Buffer.GetIterAtMark(image_position); imageEnd = imageStart; imageEnd.ForwardChar(); image_tag.LatexImage = this; buffer.ApplyTag(image_tag, imageStart, imageEnd); this.image_tag = image_tag; codeStart = image_position.Buffer.GetIterAtMark(image_position); codeStart.ForwardChar(); codeEnd = codeStart; codeEnd.ForwardChars(latex_code.Length); buffer.ApplyTag("latex_code", codeStart, codeEnd); } public bool IsImagePosition(Gtk.TextIter pos) { return image_position.Buffer.GetIterAtMark(image_position).Equal(pos); } public void Open () { Gtk.TextIter start, end; start = image_position.Buffer.GetIterAtMark(image_position); end = start; end.ForwardChar(); image_position.Buffer.RemoveTag(image_tag, start, end); image_position.Buffer.Delete(ref start, ref end); start = image_position.Buffer.GetIterAtMark(image_position); end = start; end.ForwardChars(latex_code.Length); image_position.Buffer.RemoveTag("latex_code", start, end); image_position.Buffer.DeleteMark(image_position); } } /* TODO: this tag and the pixbuf must not be copied to the clipboard */ public class LatexImageTag : DynamicNoteTag { public LatexAddin Addin; public LatexImage LatexImage; public LatexImageTag() : base() { } public override void Initialize (string element_name) { base.Initialize (element_name); CanSerialize = false; CanActivate = true; Editable = false; } protected override bool OnActivate (NoteEditor editor, Gtk.TextIter start, Gtk.TextIter end) { Addin.OpenLatexImage(LatexImage); return true; } } public class LatexAddin : NoteAddin { static LatexManager manager; static LatexAddin () { manager = new LatexManager (); } List<LatexImage> images; bool checkLatex_running; public string Title { get { return Note.Title; } } public LatexAddin () { checkLatex_running = false; images = new List<LatexImage>(); } public override void Initialize () { if (Note.TagTable.Lookup ("latex_code") == null) { NoteTag latex_code_tag = new NoteTag ("latex_code"); latex_code_tag.Invisible = true; latex_code_tag.Editable = false; latex_code_tag.CanSerialize = false; Note.TagTable.Add (latex_code_tag); } if (!Note.TagTable.IsDynamicTagRegistered ("latex_image")) { Note.TagTable.RegisterDynamicTag ("latex_image", typeof (LatexImageTag)); } } public override void Shutdown () { if (HasWindow) { Window.Editor.MoveCursor -= OnMoveCursor; } } public override void OnNoteOpened () { images = new List<LatexImage>(); CheckLaTeX (); Buffer.InsertText += OnInsertText; Buffer.DeleteRange += OnDeleteRange; Window.Editor.MoveCursor += OnMoveCursor; } public void ImageGenerated () { CheckLaTeX(); } void CheckLaTeX () { if (checkLatex_running) return; checkLatex_running = true; IDictionary<int, LatexImage> images_by_pos = null; bool images_by_pos_valid = false; try { Gtk.TextIter pos = Buffer.StartIter; pos.ForwardLine(); while (true) { Gtk.TextIter match_start = pos; Gtk.TextIter match_end = pos; Gtk.TextIter match_unused = pos; if (!pos.ForwardSearch("\\[", 0, out match_start, out match_unused, Buffer.EndIter)) { break; } if (!pos.ForwardSearch("\\]", 0, out match_unused, out match_end, Buffer.EndIter)) { break; } Gtk.TextIter image_pos = match_start; image_pos.BackwardChar(); if (!images_by_pos_valid) { images_by_pos = new Dictionary<int, LatexImage> (); foreach (LatexImage img in images) images_by_pos[img.ImagePosition.Offset] = img; images_by_pos_valid = true; /* TODO Can the Dictionary become invalid if the text * is changed by the user while this method runs? */ } Gtk.TextMark posMark = Buffer.CreateMark(null, match_end, true); LatexImage image = null; if (images_by_pos.ContainsKey(image_pos.Offset)) image = images_by_pos[image_pos.Offset]; if (CheckLatexImage(image, image_pos, match_start, match_end)) images_by_pos_valid = false; pos = Buffer.GetIterAtMark(posMark); } } finally { checkLatex_running = false; } } public void OpenLatexImage (LatexImage image) { if (images.Contains(image)) { TextIter pos = image.ImagePosition; pos.ForwardChars(3); Buffer.PlaceCursor(pos); CheckLaTeX(); } } bool CheckLatexImage(LatexImage image, Gtk.TextIter image_pos, Gtk.TextIter code_start, Gtk.TextIter code_end) { Gtk.TextIter cursor = Buffer.GetIterAtMark(Buffer.InsertMark); bool cursorInCode = (image_pos.Compare(cursor) <= 0 && code_end.Compare(cursor) >= 0); if (image != null) { if (cursorInCode) { image.Open(); images.Remove(image); return true; } return false; } else { if (cursorInCode) return false; string code = code_start.GetText(code_end); Gdk.Pixbuf pixbuf = manager.GetImage(code, this); if (pixbuf != null) { LatexImageTag image_tag = (LatexImageTag) Note.TagTable.CreateDynamicTag ("latex_image"); image_tag.Addin = this; images.Add(new LatexImage(code_start, code_end, pixbuf, image_tag)); return true; } return false; } } void OnDeleteRange (object sender, Gtk.DeleteRangeArgs args) { CheckLaTeX (); } void OnInsertText (object sender, Gtk.InsertTextArgs args) { CheckLaTeX (); } void OnMoveCursor (object sender, Gtk.MoveCursorArgs args) { /* TODO when code gets opened from the right, the cursor is positioned at * the beginning of the code */ /* TODO this is not called when the cursor is placed with the mouse */ CheckLaTeX (); } } }

