Esempio n. 1
0
		def __init__(self, file, charset):
			self.__file = file
			self.__parser = LaTeXParser()
			self.__issue_handler = CacheIssueHandler()
			self.__mtime = 0
			self.__document = None
			self.__charset = charset
			
			self.synchronize()
Esempio n. 2
0
    def init(self, file, context):
        """
		@param file: base.File
		@param context: base.WindowContext
		"""
        self._log.debug("init(%s)" % file)

        self._file = file
        self._context = context

        self._preferences = Preferences()
        self._preferences.register_monitor(
            self)  # listen to 'Show...InOutline' settings

        self.register_marker_type(
            "latex-spell",
            self._preferences.get("SpellingBackgroundColor"),
            anonymous=False)
        self.register_marker_type(
            "latex-error", self._preferences.get("ErrorBackgroundColor"))
        self.register_marker_type(
            "latex-warning", self._preferences.get("WarningBackgroundColor"))

        self._issue_view = context.find_view(self, "IssueView")
        self._outline_view = context.find_view(self, "LaTeXOutlineView")

        self._parser = LaTeXParser()
        self._outline_generator = LaTeXOutlineGenerator()
        self._validator = LaTeXValidator()
        self._document = None

        self._document_dirty = True

        # if the document is no master we display an info message on the packages to
        # include - _ensured_packages holds the already mentioned packages to not
        # annoy the user
        self._ensured_packages = []

        # spell checking
        self.__spell_checker = SpellChecker()

        #
        # initially parse
        #
        self._change_reference = self.initial_timestamp

        self.__parse()
        self.__update_neighbors()
Esempio n. 3
0
    class Entry(object):
        """
        An entry in the cache
        """
        _log = getLogger("LaTeXDocumentCache.Entry")

        def __init__(self, file, charset):
            self.__file = file
            self.__parser = LaTeXParser()
            self.__issue_handler = CacheIssueHandler()
            self.__mtime = 0
            self.__document = None
            self.__charset = charset

            self.synchronize()

        @property
        def modified(self):
            return (self.__file.mtime > self.__mtime)

        @property
        def document(self):
            return self.__document

        @property
        def issues(self):
            return self.__issue_handler.issues

        def synchronize(self):
            """
            Synchronize document model with file contents.

            @raise OSError: if the file is not found
            """
            # update timestamp
            self.__mtime = self.__file.mtime

            # clear previous data
            self.__issue_handler.clear()
            if self.__document != None:
                self.__document.destroy()
                self.__document = None

            # read file
            try:
                f = open(self.__file.path, "r")
                try:
                    content = f.read()
                finally:
                    f.close()
            except IOError:
                return

            if self.__charset is not None:
                content = content.decode(self.__charset)

            # parse
            self.__document = self.__parser.parse(content, self.__file,
                                                  self.__issue_handler)
Esempio n. 4
0
    class Entry(object):
        """
        An entry in the cache
        """
        _log = getLogger("LaTeXDocumentCache.Entry")

        def __init__(self, file, charset):
            self.__file = file
            self.__parser = LaTeXParser()
            self.__issue_handler = CacheIssueHandler()
            self.__mtime = 0
            self.__document = None
            self.__charset = charset

            self.synchronize()

        @property
        def modified(self):
            return (self.__file.mtime > self.__mtime)

        @property
        def document(self):
            return self.__document

        @property
        def issues(self):
            return self.__issue_handler.issues

        def synchronize(self):
            """
            Synchronize document model with file contents.

            @raise OSError: if the file is not found
            """
            # update timestamp
            self.__mtime = self.__file.mtime

            # clear previous data
            self.__issue_handler.clear()
            if self.__document != None:
                self.__document.destroy()
                self.__document = None

            # read file
            try:
                f = open(self.__file.path, "r")
                try:
                    content = f.read()
                finally:
                    f.close()
            except IOError:
                return

            if self.__charset is not None:
                content = content.decode(self.__charset)

            # parse
            self.__document = self.__parser.parse(content, self.__file, self.__issue_handler)
Esempio n. 5
0
        def __init__(self, file, charset):
            self.__file = file
            self.__parser = LaTeXParser()
            self.__issue_handler = CacheIssueHandler()
            self.__mtime = 0
            self.__document = None
            self.__charset = charset

            self.synchronize()
Esempio n. 6
0
    def init(self, file, context):
        """
        @param file: base.File
        @param context: base.WindowContext
        """
        LOG.debug("init(%s)" % file)

        self._file = file
        self._context = context

        self._preferences = DocumentPreferences(self._file)
        self._preferences.connect("preferences-changed",
                                  self._on_preferences_changed)

        self.register_marker_type(
            "latex-error", self._preferences.get("error-background-color"))
        self.register_marker_type(
            "latex-warning", self._preferences.get("warning-background-color"))

        self._issue_view = context.find_view(self, "IssueView")
        self._outline_view = context.find_view(self, "LaTeXOutlineView")

        self._parser = LaTeXParser()
        self._outline_generator = LaTeXOutlineGenerator()
        self._validator = LaTeXValidator()
        self._document = None

        # if the document is no master we display an info message on the packages to
        # include - _ensured_packages holds the already mentioned packages to not
        # annoy the user
        self._ensured_packages = []

        #
        # initially parse
        #
        self._change_reference = self.initial_timestamp

        GObject.idle_add(self.__parse)
        self.__update_neighbors()
Esempio n. 7
0
	def activate(self, context):
		# FIXME: use the document model of the Editor
		
		editor = context.active_editor
			
		assert type(editor) is LaTeXEditor
		
		# push environments on stack and find nearest one to close
		
		try:
			self._stack = []
			self._find_open_environments(LaTeXParser().parse(editor.content_at_left_of_cursor, None, MockIssueHandler()))
			
			if len(self._stack) > 0:
				editor.insert("\\end{%s}" % self._stack[-1])
			else:
				self._log.debug("No environment to close")
		except ValueError:
			self._log.debug("Environments are malformed")
Esempio n. 8
0
class LaTeXDependencyScanner:
    """
	This analyzes a document and recognizes its dependent files

	@deprecated:
	"""
    def __init__(self):
        self._parser = LaTeXParser()
        self._expander = LaTeXReferenceExpander()

    def scan(self, file):
        # parse
        content = open(file.path, "r").read()
        self._document = self._parser.parse(content, file, MockIssueHandler())
        self._expander.expand(self._document, file, MockIssueHandler(), None)
        # search
        self._filenames = []
        self._search(self._document)

        # TODO: add all filenames that match a regex

        return self._filenames

    def _search(self, parent):
        # search the model for all \in* commands
        for node in parent:
            if node.type == Node.COMMAND:
                if node.value.startswith("in"):
                    try:
                        argument = node.firstOfType(
                            Node.MANDATORY_ARGUMENT).innerText
                        # TODO: match against regex for filenames and create File object
                        if argument.startswith("/"):
                            filename = argument
                        else:
                            filename = node.file.dirname + "/" + argument
                        if node.value in ["include", "input"]:
                            filename += ".tex"
                        self._filenames.append(filename)
                    except IndexError:
                        pass
            self._search(node)
Esempio n. 9
0
	def init(self, file, context):
		"""
		@param file: base.File
		@param context: base.WindowContext
		"""
		self._log.debug("init(%s)" % file)

		self._file = file
		self._context = context

		self._preferences = Preferences()
		self._preferences.register_monitor(self)	# listen to 'Show...InOutline' settings

		self.register_marker_type("latex-spell", self._preferences.get("SpellingBackgroundColor"), anonymous=False)
		self.register_marker_type("latex-error", self._preferences.get("ErrorBackgroundColor"))
		self.register_marker_type("latex-warning", self._preferences.get("WarningBackgroundColor"))

		self._issue_view = context.find_view(self, "IssueView")
		self._outline_view = context.find_view(self, "LaTeXOutlineView")

		self._parser = LaTeXParser()
		self._outline_generator = LaTeXOutlineGenerator()
		self._validator = LaTeXValidator()
		self._document = None

		self._document_dirty = True

		# if the document is no master we display an info message on the packages to
		# include - _ensured_packages holds the already mentioned packages to not
		# annoy the user
		self._ensured_packages = []

		# spell checking
		self.__spell_checker = SpellChecker()

		#
		# initially parse
		#
		self._change_reference = self.initial_timestamp

		self.__parse()
		self.__update_neighbors()
Esempio n. 10
0
class LaTeXDependencyScanner:
	"""
	This analyzes a document and recognizes its dependent files

	@deprecated:
	"""
	def __init__(self):
		self._parser = LaTeXParser()
		self._expander = LaTeXReferenceExpander()

	def scan(self, file):
		# parse
		content = open(file.path, "r").read()
		self._document = self._parser.parse(content, file, MockIssueHandler())
		self._expander.expand(self._document, file, MockIssueHandler(), None)
		# search
		self._filenames = []
		self._search(self._document)

		# TODO: add all filenames that match a regex

		return self._filenames

	def _search(self, parent):
		# search the model for all \in* commands
		for node in parent:
			if node.type == Node.COMMAND:
				if node.value.startswith("in"):
					try:
						argument = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
						# TODO: match against regex for filenames and create File object
						if argument.startswith("/"):
							filename = argument
						else:
							filename = node.file.dirname + "/" + argument
						if node.value in ["include", "input"]:
							filename += ".tex"
						self._filenames.append(filename)
					except IndexError:
						pass
			self._search(node)
Esempio n. 11
0
    def init(self, file, context):
        """
        @param file: base.File
        @param context: base.WindowContext
        """
        LOG.debug("init(%s)" % file)

        self._file = file
        self._context = context

        self._preferences = DocumentPreferences(self._file)
        self._preferences.connect("preferences-changed", self._on_preferences_changed)

        self.register_marker_type("latex-error", self._preferences.get("error-background-color"))
        self.register_marker_type("latex-warning", self._preferences.get("warning-background-color"))

        self._issue_view = context.find_view(self, "IssueView")
        self._outline_view = context.find_view(self, "LaTeXOutlineView")

        self._parser = LaTeXParser()
        self._outline_generator = LaTeXOutlineGenerator()
        self._validator = LaTeXValidator()
        self._document = None

        # if the document is no master we display an info message on the packages to
        # include - _ensured_packages holds the already mentioned packages to not
        # annoy the user
        self._ensured_packages = []

        #
        # initially parse
        #
        self._change_reference = self.initial_timestamp

        GObject.idle_add(self.__parse)
        self.__update_neighbors()
Esempio n. 12
0
class LaTeXEditor(Editor, IIssueHandler):

    extensions = Preferences().get("latex-extensions").split(",")

    dnd_extensions = [".png", ".pdf", ".bib", ".tex"]

    @property
    def completion_handlers(self):
        self.__latex_completion_handler = LaTeXCompletionHandler()
        return [ self.__latex_completion_handler ]

    def init(self, file, context):
        """
        @param file: base.File
        @param context: base.WindowContext
        """
        LOG.debug("init(%s)" % file)

        self._file = file
        self._context = context

        self._preferences = DocumentPreferences(self._file)
        self._preferences.connect("preferences-changed", self._on_preferences_changed)

        self.register_marker_type("latex-error", self._preferences.get("error-background-color"))
        self.register_marker_type("latex-warning", self._preferences.get("warning-background-color"))

        self._issue_view = context.find_view(self, "IssueView")
        self._outline_view = context.find_view(self, "LaTeXOutlineView")

        self._parser = LaTeXParser()
        self._outline_generator = LaTeXOutlineGenerator()
        self._validator = LaTeXValidator()
        self._document = None

        # if the document is no master we display an info message on the packages to
        # include - _ensured_packages holds the already mentioned packages to not
        # annoy the user
        self._ensured_packages = []

        #
        # initially parse
        #
        self._change_reference = self.initial_timestamp

        GObject.idle_add(self.__parse)
        self.__update_neighbors()

    def _on_preferences_changed(self, prefs, key, new_value):
        if key in ["outline-show-labels", "outline-show-tables", "outline-show-graphics"]:
            # regenerate outline model
            if self._document_is_master:
                self._outline = self._outline_generator.generate(self._document, self)
                self._outline_view.set_outline(self._outline)
            else:
                # FIXME: self._document contains the full model of child and master
                # so we may not use it for regenerating the outline here
                self.__parse()

    def drag_drop_received(self, files):
        # see base.Editor.drag_drop_received

        # TODO: we need to insert the source at the drop location - so pass it here

        LOG.debug("drag_drop: %s" % files)

#        if len(files) == 1:
#            file = files[0]
#            self._log.debug("Got one file: %s, extension: %s" % (file.path, file.extension))
#            if file.extension == ".png":
#                self._log.debug("PNG image - including...")
#                source = "\\includegraphics{%s}" % file.path
#                self.insert(source)

    def insert(self, source):
        # see base.Editor.insert

        if type(source) is LaTeXSource:
            if source.packages and len(source.packages) > 0:
                self.ensure_packages(source.packages)

            Editor.insert(self, source.source)
        else:
            Editor.insert(self, source)

    POSITION_PACKAGES, POSITION_BIBLIOGRAPHY = 1, 2

    def insert_at_position(self, source, position):
        """
        Insert source at a certain position in the LaTeX document:

         * POSITION_PACKAGES: after the last \usepackage statement
         * POSITION_BIBLIOGRAPHY: before \end{document}

        @param source: a LaTeXSource object
        @param position: POSITION_PACKAGES | POSITION_BIBLIOGRAPHY
        """

        if position == self.POSITION_BIBLIOGRAPHY:
            offset = self._document.end_of_document
            Editor.insert_at_offset(self, offset, source, True)
        elif position == self.POSITION_PACKAGES:
            offset = self._document.end_of_packages
            Editor.insert_at_offset(self, offset, source, False)
        else:
            raise NotImplementedError

    def ensure_packages(self, packages):
        """
        Ensure that certain packages are included

        @param packages: a list of package names
        """
        self.__parse()    # ensure up-to-date document model

        if not self._document_is_master:
            LOG.debug("ensure_packages: document is not a master")

            # find the packages that haven't already been mentioned
            info_packages = [p for p in packages if not p in self._ensured_packages]

            if len(info_packages) > 0:
                # generate markup
                li_tags = "\n".join([" • <tt>%s</tt>" % p for p in info_packages])

                from ..util import open_info
                open_info("LaTeX Package Required",
                        "Please make sure that the following packages are included in the master document per <tt>\\usepackage</tt>: \n\n%s" % li_tags)

                # extend the already mentioned packages
                self._ensured_packages.extend(info_packages)

            return

        # find missing packages
        present_package_names = [p.value for p in self._outline.packages]
        package_names = [p for p in packages if not p in present_package_names]

        # insert the necessary \usepackage commands
        if len(package_names) > 0:
            source = "\n" + "\n".join(["\\usepackage{%s}" % n for n in package_names])
            self.insert_at_position(source, self.POSITION_PACKAGES)

    def on_save(self):
        """
        The file has been saved

        Update models
        """

#        from multiprocessing import Process
#
#        p_parse = Process(target=self.__parse)
#        p_parse.start()

        self.__parse()

    def __update_neighbors(self):
        """
        Find all files in the working directory that are relevant for LaTeX, e.g.
        other *.tex files or images.
        """

        # TODO: this is only needed to feed the LaTeXCompletionHandler. So maybe it should
        # know the edited file and the Editor should call an update() method of the handler
        # when the file is saved.

        #FIXME: Look in graphicspath here, and in subdirs.

        tex_files = self._file.find_neighbors(".tex")
        bib_files = self._file.find_neighbors(".bib")

        graphic_files = []
        for extension in [".ps", ".pdf", ".png", ".jpg", ".eps"]:
            graphic_files.extend(self._file.find_neighbors(extension))

        self.__latex_completion_handler.set_neighbors(tex_files, bib_files, graphic_files)

    #@verbose
    def __parse(self):
        """
        Ensure that the document model is up-to-date
        """
        if self.content_changed(self._change_reference):
            # content has changed so document model may be dirty
            self._change_reference = self.current_timestamp

            LOG.debug("Parsing document...")

            # reset highlight
            self.remove_markers("latex-error")
            self.remove_markers("latex-warning")

            # reset issues
            self._issue_view.clear()

            if BENCHMARK: t = time.clock()

            # parse document
            if self._document != None:
                self._document.destroy()
                del self._document
            self._document = self._parser.parse(self.content, self._file, self)

            # update document preferences
            self._preferences.parse_content(self.content)

            if BENCHMARK: LOG.info("LaTeXParser.parse: %f" % (time.clock() - t))

            LOG.debug("Parsed %s bytes of content" % len(self.content))

            # FIXME: the LaTeXChooseMasterAction enabled state has to be updated on tab change, too!

            if self._document.is_master:

                self._context.set_action_enabled("LaTeXChooseMasterAction", False)
                self._document_is_master = True

                # expand child documents
                expander = LaTeXReferenceExpander()
                expander.expand(self._document, self._file, self, self.charset)

                # generate outline from the expanded model
                self._outline = self._outline_generator.generate(self._document, self)

                # pass to view
                self._outline_view.set_outline(self._outline)

                # validate
                self._validator.validate(self._document, self._outline, self, self._preferences)
            else:
                LOG.debug("Document is not a master")

                self._context.set_action_enabled("LaTeXChooseMasterAction", True)
                self._document_is_master = False

                # the outline used by the outline view has to be created only from the child model
                # otherwise we see the outline of the master and get wrong offsets
                self._outline = self._outline_generator.generate(self._document, self)
                self._outline_view.set_outline(self._outline)

                # find master
                master_file = self.__master_file

                if master_file is None:
                    return

                # parse master
                master_content = open(master_file.path).read()
                self._document = self._parser.parse(master_content, master_file, self)

                # expand its child documents
                expander = LaTeXReferenceExpander()
                expander.expand(self._document, master_file, self, self.charset)

                # create another outline of the expanded master model to make elements
                # from the master available (labels, colors, BibTeX files etc.)
                self._outline = self._outline_generator.generate(self._document, self)

                # validate
                prefs = DocumentPreferences(master_file)
                prefs.parse_content(master_content)
                self._validator.validate(self._document, self._outline, self, prefs)

            # pass outline to completion
            self.__latex_completion_handler.set_outline(self._outline)

            # pass neighbor files to completion
            self.__update_neighbors()

            LOG.debug("Parsing finished")

    def choose_master_file(self):
        master_filename = ChooseMasterDialog().run(self._file.dirname)
        if master_filename:
            # relativize the master filename
            master_filename = File(master_filename).relativize(self._file.dirname, True)
            self._preferences.set("master-filename", master_filename)
        return master_filename

    @property
    def __master_file(self):
        """
        Find the LaTeX master of this child

        @return: base.File
        """
        path = self._preferences.get("master-filename")
        if path != None:
            if File.is_absolute(path):
                LOG.debug("master path is absolute")
                return File(path)
            else:
                LOG.debug("master path is relative")
                return File.create_from_relative_path(path, self._file.dirname)
        else:
            # master filename not found, ask user
            master_filename = self.choose_master_file()
            if master_filename:
                return File.create_from_relative_path(master_filename, self._file.dirname)
            else:
                return None

    def issue(self, issue):
        # see IIssueHandler.issue

        local = (issue.file == self._file)

        self._issue_view.append_issue(issue, local)

        if issue.file == self._file:
            if issue.severity == Issue.SEVERITY_ERROR:
                self.create_marker("latex-error", issue.start, issue.end)
            elif issue.severity == Issue.SEVERITY_WARNING:
                self.create_marker("latex-warning", issue.start, issue.end)

    def on_cursor_moved(self, offset):
        """
        The cursor has moved
        """
        if self._preferences.get("outline-connect-to-editor"):
            self._outline_view.select_path_by_offset(offset)

    @property
    def file(self):
        # overrides Editor.file

        # we may not call self._document.is_master because _document is always
        # replaced by the master model
        if self._document_is_master:
            return self._file
        else:
            return self.__master_file

    @property
    def edited_file(self):
        """
        Always returns the really edited file instead of the master

        This is called by the outline view to identify foreign nodes
        """
        return self._file

    def destroy(self):
        # unreference the window context
        del self._context

        # destroy the cached document
        self._document.destroy()
        del self._document

        Editor.destroy(self)
Esempio n. 13
0
class LaTeXEditor(Editor, IIssueHandler, IMisspelledWordHandler,
                  IPreferencesMonitor):

    _log = getLogger("LaTeXEditor")

    #extensions = [".tex"]
    extensions = Preferences().get("LatexExtensions", ".tex").split(" ")

    dnd_extensions = [".png", ".pdf", ".bib", ".tex"]

    @property
    def completion_handlers(self):
        self.__latex_completion_handler = LaTeXCompletionHandler()
        self.__snippet_completion_handler = SnippetCompletionHandler()

        return [
            self.__latex_completion_handler, self.__snippet_completion_handler
        ]

    def init(self, file, context):
        """
		@param file: base.File
		@param context: base.WindowContext
		"""
        self._log.debug("init(%s)" % file)

        self._file = file
        self._context = context

        self._preferences = Preferences()
        self._preferences.register_monitor(
            self)  # listen to 'Show...InOutline' settings

        self.register_marker_type(
            "latex-spell",
            self._preferences.get("SpellingBackgroundColor"),
            anonymous=False)
        self.register_marker_type(
            "latex-error", self._preferences.get("ErrorBackgroundColor"))
        self.register_marker_type(
            "latex-warning", self._preferences.get("WarningBackgroundColor"))

        self._issue_view = context.find_view(self, "IssueView")
        self._outline_view = context.find_view(self, "LaTeXOutlineView")

        self._parser = LaTeXParser()
        self._outline_generator = LaTeXOutlineGenerator()
        self._validator = LaTeXValidator()
        self._document = None

        self._document_dirty = True

        # if the document is no master we display an info message on the packages to
        # include - _ensured_packages holds the already mentioned packages to not
        # annoy the user
        self._ensured_packages = []

        # spell checking
        self.__spell_checker = SpellChecker()

        #
        # initially parse
        #
        self._change_reference = self.initial_timestamp

        self.__parse()
        self.__update_neighbors()

    def _on_value_changed(self, key, new_value):
        # see preferences.IPreferencesMonitor._on_value_changed

        if key in [
                "ShowLabelsInOutline", "ShowTablesInOutline",
                "ShowGraphicsInOutline"
        ]:
            # regenerate outline model
            if self._document_is_master:
                self._outline = self._outline_generator.generate(
                    self._document, self)
                self._outline_view.set_outline(self._outline)
            else:
                # FIXME: self._document contains the full model of child and master
                # so we may not use it for regenerating the outline here
                self.__parse()
        elif key == "ShowLatexToolbar":
            show_toolbar = self._preferences.get_bool("ShowLatexToolbar", True)
            if show_toolbar:
                self._window_context._window_decorator._toolbar.show()
            else:
                self._window_context._window_decorator._toolbar.hide()

    def _ctrl_left_clicked(self, it):
        """
		For synchronization by synctex
		"""
        # Editor._ctrl_left_clicked

        tab = self.tab_decorator.tab
        line = it.get_line() + 1
        column = it.get_line_offset() + 1
        source_file = "%s/%s" % (self._file.dirname, self._file.basename)
        # We use self.file and not self._file here, to get the
        # master document, because the output file will have the
        # name of the master document
        try:
            output_file = "%s.pdf" % self.file.shortname
        except:
            # Is this due to a bug ? Or is there a method I
            # don't know to check wether a file has/is a
            # master file or doesn't/couldn't have one ?
            self.__log.debug(
                "Error while trying to get the output file path. No master document ?"
            )
            return

        from livepreview import LaTeXPreviews

        if self._window_context.latex_previews == None:
            self._window_context.latex_previews = LaTeXPreviews(
                self._window_context)

        latex_previews = self._window_context.latex_previews
        latex_previews.sync_view(tab, source_file, line, column, output_file)

    def drag_drop_received(self, files):
        # see base.Editor.drag_drop_received

        # TODO: we need to insert the source at the drop location - so pass it here

        self._log.debug("drag_drop: %s" % files)

#		if len(files) == 1:
#			file = files[0]
#			self._log.debug("Got one file: %s, extension: %s" % (file.path, file.extension))
#			if file.extension == ".png":
#				self._log.debug("PNG image - including...")
#				source = "\\includegraphics{%s}" % file.path
#				self.insert(source)

    def insert(self, source):
        # see base.Editor.insert

        if type(source) is LaTeXSource:
            if source.packages and len(source.packages) > 0:
                self.ensure_packages(source.packages)

            Editor.insert(self, source.source)
        else:
            Editor.insert(self, source)

    POSITION_PACKAGES, POSITION_BIBLIOGRAPHY = 1, 2

    def insert_at_position(self, source, position):
        """
		Insert source at a certain position in the LaTeX document:

		 * POSITION_PACKAGES: after the last \usepackage statement
		 * POSITION_BIBLIOGRAPHY: before \end{document}

		@param source: a LaTeXSource object
		@param position: POSITION_PACKAGES | POSITION_BIBLIOGRAPHY
		"""

        if position == self.POSITION_BIBLIOGRAPHY:
            offset = self._document.end_of_document
            Editor.insert_at_offset(self, offset, source, True)
        elif position == self.POSITION_PACKAGES:
            offset = self._document.end_of_packages
            Editor.insert_at_offset(self, offset, source, False)
        else:
            raise NotImplementedError

    def ensure_packages(self, packages):
        """
		Ensure that certain packages are included

		@param packages: a list of package names
		"""
        self.__parse()  # ensure up-to-date document model

        if not self._document_is_master:
            self._log.debug("ensure_packages: document is not a master")

            # find the packages that haven't already been mentioned
            info_packages = [
                p for p in packages if not p in self._ensured_packages
            ]

            if len(info_packages) > 0:
                # generate markup
                li_tags = "\n".join(
                    [" • <tt>%s</tt>" % p for p in info_packages])

                from ..util import open_info
                open_info(
                    "LaTeX Package Required",
                    "Please make sure that the following packages are included in the master document per <tt>\\usepackage</tt>: \n\n%s"
                    % li_tags)

                # extend the already mentioned packages
                self._ensured_packages.extend(info_packages)

            return

        # find missing packages
        present_package_names = [p.value for p in self._outline.packages]
        package_names = [p for p in packages if not p in present_package_names]

        # insert the necessary \usepackage commands
        if len(package_names) > 0:
            source = "\n" + "\n".join(
                ["\\usepackage{%s}" % n for n in package_names])
            self.insert_at_position(source, self.POSITION_PACKAGES)

    def on_save(self):
        """
		The file has been saved

		Update models
		"""

        #		from multiprocessing import Process
        #
        #		p_parse = Process(target=self.__parse)
        #		p_parse.start()

        self.__parse()

    def __update_neighbors(self):
        """
		Find all files in the working directory that are relevant for LaTeX, e.g.
		other *.tex files or images.
		"""

        # TODO: this is only needed to feed the LaTeXCompletionHandler. So maybe it should
        # know the edited file and the Editor should call an update() method of the handler
        # when the file is saved.

        tex_files = self._file.find_neighbors(".tex")
        bib_files = self._file.find_neighbors(".bib")

        graphic_files = []
        for extension in [".ps", ".pdf", ".png", ".jpg", ".eps"]:
            graphic_files.extend(self._file.find_neighbors(extension))

        self.__latex_completion_handler.set_neighbors(tex_files, bib_files,
                                                      graphic_files)

    #@verbose
    def __parse(self):
        """
		Ensure that the document model is up-to-date
		"""
        if self.content_changed(self._change_reference):
            # content has changed so document model may be dirty
            self._change_reference = self.current_timestamp

            self._log.debug("Parsing document...")

            # reset highlight
            self.remove_markers("latex-error")
            self.remove_markers("latex-warning")

            # reset issues
            self._issue_view.clear()

            if BENCHMARK: t = time.clock()

            # parse document
            if self._document != None:
                self._document.destroy()
                del self._document
            self._document = self._parser.parse(self.content, self._file, self)

            if BENCHMARK:
                self._log.info("LaTeXParser.parse: %f" % (time.clock() - t))

            # create a copy that won't be expanded (e.g. for spell check)
            #self._local_document = deepcopy(self._document)

            self._log.debug("Parsed %s bytes of content" % len(self.content))

            # FIXME: the LaTeXChooseMasterAction enabled state has to be updated on tab change, too!

            if self._document.is_master:

                self._context.set_action_enabled("LaTeXChooseMasterAction",
                                                 False)
                self._document_is_master = True

                # expand child documents
                expander = LaTeXReferenceExpander()
                expander.expand(self._document, self._file, self, self.charset)

                # generate outline from the expanded model
                self._outline = self._outline_generator.generate(
                    self._document, self)

                # pass to view
                self._outline_view.set_outline(self._outline)

                # validate
                self._validator.validate(self._document, self._outline, self)
            else:
                self._log.debug("Document is not a master")

                self._context.set_action_enabled("LaTeXChooseMasterAction",
                                                 True)
                self._document_is_master = False

                # the outline used by the outline view has to be created only from the child model
                # otherwise we see the outline of the master and get wrong offsets
                self._outline = self._outline_generator.generate(
                    self._document, self)
                self._outline_view.set_outline(self._outline)

                # find master
                master_file = self.__master_file

                if master_file is None:
                    return

                # parse master
                master_content = open(master_file.path).read()
                self._document = self._parser.parse(master_content,
                                                    master_file, self)

                # expand its child documents
                expander = LaTeXReferenceExpander()
                expander.expand(self._document, master_file, self,
                                self.charset)

                # create another outline of the expanded master model to make elements
                # from the master available (labels, colors, BibTeX files etc.)
                self._outline = self._outline_generator.generate(
                    self._document, self)

                # validate
                self._validator.validate(self._document, self._outline, self)

            # pass outline to completion
            self.__latex_completion_handler.set_outline(self._outline)

            # pass neighbor files to completion
            self.__update_neighbors()

            self._log.debug("Parsing finished")

            #print self._document.xml

    @property
    @returns(File)
    def __master_file(self):
        """
		Find the LaTeX master of this child

		@return: base.File
		"""
        # TODO: cache result

        property_file = PropertyFile(self._file)
        try:
            #return File(property_file["MasterFilename"])

            path = property_file["MasterFilename"]
            # the property file may contain absolute and relative paths
            # because we switched in 0.2rc2
            if File.is_absolute(path):
                self._log.debug("Path is absolute")
                return File(path)
            else:
                self._log.debug("Path is relative")
                return File.create_from_relative_path(path, self._file.dirname)
        except KeyError:  # master filename not found
            # ask user
            master_filename = ChooseMasterDialog().run(self._file.dirname)
            if master_filename:
                # relativize the master filename
                master_filename = File(master_filename).relativize(
                    self._file.dirname, True)

                property_file["MasterFilename"] = master_filename
                property_file.save()
                return File(master_filename)
            else:
                # no master file chosen
                return None

    def issue(self, issue):
        # see IIssueHandler.issue

        local = (issue.file == self._file)

        self._issue_view.append_issue(issue, local)

        if issue.file == self._file:
            if issue.severity == Issue.SEVERITY_ERROR:
                self.create_marker("latex-error", issue.start, issue.end)
            elif issue.severity == Issue.SEVERITY_WARNING:
                self.create_marker("latex-warning", issue.start, issue.end)

    #
    # spell checking begin
    #
    # TODO: put this in a SpellCheckDelegate or so
    #

    def spell_check(self):
        """
		Run a spell check on the file

		@raise ImportError: if pyenchant is not installed
		"""
        self.remove_markers("latex-spell")
        self.__word_markers = {}

        #
        # FIXME: it makes no sense to pass _document here because it contains
        # the expanded model of the document. We must keep the the not expanded
        # one, too.
        #
        try:
            self.__spell_checker.run(self._document, self.edited_file, self)
        except ImportError:
            open_error(
                "Enchant is missing",
                "The enchant library and its Python bindings (package is propably called pyenchant) are needed for spell checking but could not be found"
            )

    def on_misspelled_word(self, word, position):
        # see IMisspelledWordHandler.on_misspelled_word
        marker = self.create_marker("latex-spell", position,
                                    position + len(word))
        self.__word_markers[marker.id] = word

    def on_marker_activated(self, marker, event):
        """
		A marker has been activated
		"""
        #self._log.debug("activate_marker(%s, %s)" % (marker, event))

        if marker.type == "latex-spell":
            word = self.__word_markers[marker.id]
            suggestions = self.__spell_checker.find_suggestions(word)

            self._log.debug(str(suggestions))

            # build and show the context menu
            menu = self.__get_suggestions_menu(word, suggestions, marker)
            menu.popup(None, None, None, event.button, event.time)

            # swallow the signal so that the original context menu
            # isn't shown
            return True

    def __get_suggestions_menu(self, word, suggestions, marker):
        """
		Return the context menu for spell check suggestions

		@param word: the misspelled word
		@param suggestions: a list of suggested words
		@param marker: the activated Marker
		"""
        suggestions_menu = gtk.Menu()

        suggestions_menu.add(gtk.SeparatorMenuItem())

        item_add = gtk.ImageMenuItem(gtk.STOCK_ADD)
        item_add.connect("activate", self.__on_add_word_activated, marker,
                         word)
        suggestions_menu.add(item_add)

        suggestions_menu.add(gtk.SeparatorMenuItem())

        item_abort = gtk.ImageMenuItem(gtk.STOCK_CANCEL)
        item_abort.connect("activate", self.__on_abort_spell_check_activated)
        suggestions_menu.add(item_abort)

        suggestions_menu.show_all()

        # add suggestions
        suggestions.reverse()  # we insert in reverse order, so reverse before

        for suggestion in suggestions:
            if suggestion.user_defined:
                item = gtk.ImageMenuItem(suggestion.word)
                item.set_image(
                    gtk.image_new_from_stock(gtk.STOCK_FLOPPY,
                                             gtk.ICON_SIZE_MENU))
            else:
                item = gtk.MenuItem(suggestion.word)

            item.connect("activate", self.__on_suggestion_activated,
                         suggestion, marker)
            suggestions_menu.insert(item, 0)
            item.show()

        return suggestions_menu

    def __on_suggestion_activated(self, menu_item, suggestion, marker):
        """
		A suggestion from the context menu has been activated

		@param menu_item: the activated MenuItem
		@param suggestion: the word
		"""
        self.replace_marker_content(marker, suggestion.word)

    def __on_abort_spell_check_activated(self, menu_item):
        """
		"""
        self.remove_markers("latex-spell")

    def __on_add_word_activated(self, menu_item, marker, word):
        """
		Add a word to the dictionary

		@param marker: the marker for the word
		@param word: the checked word
		"""
        self.__spell_checker.add_word(word)
        self.remove_marker(marker)

    #
    # spell checking end
    #

    def on_cursor_moved(self, offset):
        """
		The cursor has moved
		"""
        if self._preferences.get_bool("ConnectOutlineToEditor", True):
            self._outline_view.select_path_by_offset(offset)

    @property
    def file(self):
        # overrides Editor.file

        # we may not call self._document.is_master because _document is always
        # replaced by the master model
        if self._document_is_master:
            return self._file
        else:
            return self.__master_file

    @property
    def edited_file(self):
        """
		Always returns the really edited file instead of the master

		This is called by the outline view to identify foreign nodes
		"""
        return self._file

    def destroy(self):
        # stop listening preferences
        self._preferences.remove_monitor(self)

        # unreference the window context
        del self._context

        # destroy the cached document
        self._document.destroy()
        del self._document

        Editor.destroy(self)

    def __del__(self):
        self._log.debug("Properly destroyed %s" % self)
Esempio n. 14
0
class LaTeXEditor(Editor, IIssueHandler, IMisspelledWordHandler, IPreferencesMonitor):

	_log = getLogger("LaTeXEditor")

	#extensions = [".tex"]
	extensions = Preferences().get("LatexExtensions", ".tex").split(" ")

	dnd_extensions = [".png", ".pdf", ".bib", ".tex"]

	@property
	def completion_handlers(self):
		self.__latex_completion_handler = LaTeXCompletionHandler()
		self.__snippet_completion_handler = SnippetCompletionHandler()

		return [ self.__latex_completion_handler, self.__snippet_completion_handler ]

	def init(self, file, context):
		"""
		@param file: base.File
		@param context: base.WindowContext
		"""
		self._log.debug("init(%s)" % file)

		self._file = file
		self._context = context

		self._preferences = Preferences()
		self._preferences.register_monitor(self)	# listen to 'Show...InOutline' settings

		self.register_marker_type("latex-spell", self._preferences.get("SpellingBackgroundColor"), anonymous=False)
		self.register_marker_type("latex-error", self._preferences.get("ErrorBackgroundColor"))
		self.register_marker_type("latex-warning", self._preferences.get("WarningBackgroundColor"))

		self._issue_view = context.find_view(self, "IssueView")
		self._outline_view = context.find_view(self, "LaTeXOutlineView")

		self._parser = LaTeXParser()
		self._outline_generator = LaTeXOutlineGenerator()
		self._validator = LaTeXValidator()
		self._document = None

		self._document_dirty = True

		# if the document is no master we display an info message on the packages to
		# include - _ensured_packages holds the already mentioned packages to not
		# annoy the user
		self._ensured_packages = []

		# spell checking
		self.__spell_checker = SpellChecker()

		#
		# initially parse
		#
		self._change_reference = self.initial_timestamp

		self.__parse()
		self.__update_neighbors()

	def _on_value_changed(self, key, new_value):
		# see preferences.IPreferencesMonitor._on_value_changed

		if key in ["ShowLabelsInOutline", "ShowTablesInOutline", "ShowGraphicsInOutline"]:
			# regenerate outline model
			if self._document_is_master:
				self._outline = self._outline_generator.generate(self._document, self)
				self._outline_view.set_outline(self._outline)
			else:
				# FIXME: self._document contains the full model of child and master
				# so we may not use it for regenerating the outline here
				self.__parse()
		elif key == "ShowLatexToolbar":
			show_toolbar = self._preferences.get_bool("ShowLatexToolbar", True)
			if show_toolbar:
				self._window_context._window_decorator._toolbar.show()
			else:
				self._window_context._window_decorator._toolbar.hide()

	def _ctrl_left_clicked(self, it):
		"""
		For synchronization by synctex
		"""
		# Editor._ctrl_left_clicked

		tab = self.tab_decorator.tab
		line = it.get_line() + 1
		column = it.get_line_offset() + 1
		source_file = "%s/%s" % (self._file.dirname, self._file.basename)
		# We use self.file and not self._file here, to get the
		# master document, because the output file will have the
		# name of the master document
		try:
			output_file = "%s.pdf" % self.file.shortname
		except:
			# Is this due to a bug ? Or is there a method I
			# don't know to check wether a file has/is a
			# master file or doesn't/couldn't have one ?
			self.__log.debug("Error while trying to get the output file path. No master document ?")
			return

		from livepreview import LaTeXPreviews

		if self._window_context.latex_previews == None:
			self._window_context.latex_previews = LaTeXPreviews(self._window_context)

		latex_previews = self._window_context.latex_previews
		latex_previews.sync_view(tab, source_file, line, column, output_file)

	def drag_drop_received(self, files):
		# see base.Editor.drag_drop_received

		# TODO: we need to insert the source at the drop location - so pass it here

		self._log.debug("drag_drop: %s" % files)

#		if len(files) == 1:
#			file = files[0]
#			self._log.debug("Got one file: %s, extension: %s" % (file.path, file.extension))
#			if file.extension == ".png":
#				self._log.debug("PNG image - including...")
#				source = "\\includegraphics{%s}" % file.path
#				self.insert(source)

	def insert(self, source):
		# see base.Editor.insert

		if type(source) is LaTeXSource:
			if source.packages and len(source.packages) > 0:
				self.ensure_packages(source.packages)

			Editor.insert(self, source.source)
		else:
			Editor.insert(self, source)

	POSITION_PACKAGES, POSITION_BIBLIOGRAPHY = 1, 2

	def insert_at_position(self, source, position):
		"""
		Insert source at a certain position in the LaTeX document:

		 * POSITION_PACKAGES: after the last \usepackage statement
		 * POSITION_BIBLIOGRAPHY: before \end{document}

		@param source: a LaTeXSource object
		@param position: POSITION_PACKAGES | POSITION_BIBLIOGRAPHY
		"""

		if position == self.POSITION_BIBLIOGRAPHY:
			offset = self._document.end_of_document
			Editor.insert_at_offset(self, offset, source, True)
		elif position == self.POSITION_PACKAGES:
			offset = self._document.end_of_packages
			Editor.insert_at_offset(self, offset, source, False)
		else:
			raise NotImplementedError

	def ensure_packages(self, packages):
		"""
		Ensure that certain packages are included

		@param packages: a list of package names
		"""
		self.__parse()	# ensure up-to-date document model

		if not self._document_is_master:
			self._log.debug("ensure_packages: document is not a master")

			# find the packages that haven't already been mentioned
			info_packages = [p for p in packages if not p in self._ensured_packages]

			if len(info_packages) > 0:
				# generate markup
				li_tags = "\n".join([" • <tt>%s</tt>" % p for p in info_packages])

				from ..util import open_info
				open_info("LaTeX Package Required",
						"Please make sure that the following packages are included in the master document per <tt>\\usepackage</tt>: \n\n%s" % li_tags)

				# extend the already mentioned packages
				self._ensured_packages.extend(info_packages)

			return

		# find missing packages
		present_package_names = [p.value for p in self._outline.packages]
		package_names = [p for p in packages if not p in present_package_names]

		# insert the necessary \usepackage commands
		if len(package_names) > 0:
			source = "\n" + "\n".join(["\\usepackage{%s}" % n for n in package_names])
			self.insert_at_position(source, self.POSITION_PACKAGES)

	def on_save(self):
		"""
		The file has been saved

		Update models
		"""

#		from multiprocessing import Process
#
#		p_parse = Process(target=self.__parse)
#		p_parse.start()

		self.__parse()

	def __update_neighbors(self):
		"""
		Find all files in the working directory that are relevant for LaTeX, e.g.
		other *.tex files or images.
		"""

		# TODO: this is only needed to feed the LaTeXCompletionHandler. So maybe it should
		# know the edited file and the Editor should call an update() method of the handler
		# when the file is saved.

		tex_files = self._file.find_neighbors(".tex")
		bib_files = self._file.find_neighbors(".bib")

		graphic_files = []
		for extension in [".ps", ".pdf", ".png", ".jpg", ".eps"]:
			graphic_files.extend(self._file.find_neighbors(extension))

		self.__latex_completion_handler.set_neighbors(tex_files, bib_files, graphic_files)

	#@verbose
	def __parse(self):
		"""
		Ensure that the document model is up-to-date
		"""
		if self.content_changed(self._change_reference):
			# content has changed so document model may be dirty
			self._change_reference = self.current_timestamp

			self._log.debug("Parsing document...")

			# reset highlight
			self.remove_markers("latex-error")
			self.remove_markers("latex-warning")

			# reset issues
			self._issue_view.clear()

			if BENCHMARK: t = time.clock()

			# parse document
			if self._document != None:
				self._document.destroy()
				del self._document
			self._document = self._parser.parse(self.content, self._file, self)

			if BENCHMARK: self._log.info("LaTeXParser.parse: %f" % (time.clock() - t))

			# create a copy that won't be expanded (e.g. for spell check)
			#self._local_document = deepcopy(self._document)

			self._log.debug("Parsed %s bytes of content" % len(self.content))

			# FIXME: the LaTeXChooseMasterAction enabled state has to be updated on tab change, too!

			if self._document.is_master:

				self._context.set_action_enabled("LaTeXChooseMasterAction", False)
				self._document_is_master = True

				# expand child documents
				expander = LaTeXReferenceExpander()
				expander.expand(self._document, self._file, self, self.charset)

				# generate outline from the expanded model
				self._outline = self._outline_generator.generate(self._document, self)

				# pass to view
				self._outline_view.set_outline(self._outline)

				# validate
				self._validator.validate(self._document, self._outline, self)
			else:
				self._log.debug("Document is not a master")

				self._context.set_action_enabled("LaTeXChooseMasterAction", True)
				self._document_is_master = False

				# the outline used by the outline view has to be created only from the child model
				# otherwise we see the outline of the master and get wrong offsets
				self._outline = self._outline_generator.generate(self._document, self)
				self._outline_view.set_outline(self._outline)

				# find master
				master_file = self.__master_file

				if master_file is None:
					return

				# parse master
				master_content = open(master_file.path).read()
				self._document = self._parser.parse(master_content, master_file, self)

				# expand its child documents
				expander = LaTeXReferenceExpander()
				expander.expand(self._document, master_file, self, self.charset)

				# create another outline of the expanded master model to make elements
				# from the master available (labels, colors, BibTeX files etc.)
				self._outline = self._outline_generator.generate(self._document, self)

				# validate
				self._validator.validate(self._document, self._outline, self)

			# pass outline to completion
			self.__latex_completion_handler.set_outline(self._outline)

			# pass neighbor files to completion
			self.__update_neighbors()

			self._log.debug("Parsing finished")

			#print self._document.xml

	@property
	@returns(File)
	def __master_file(self):
		"""
		Find the LaTeX master of this child

		@return: base.File
		"""
		# TODO: cache result

		property_file = PropertyFile(self._file)
		try:
			#return File(property_file["MasterFilename"])

			path = property_file["MasterFilename"]
			# the property file may contain absolute and relative paths
			# because we switched in 0.2rc2
			if File.is_absolute(path):
				self._log.debug("Path is absolute")
				return File(path)
			else:
				self._log.debug("Path is relative")
				return File.create_from_relative_path(path, self._file.dirname)
		except KeyError:		# master filename not found
			# ask user
			master_filename = ChooseMasterDialog().run(self._file.dirname)
			if master_filename:
				# relativize the master filename
				master_filename = File(master_filename).relativize(self._file.dirname, True)

				property_file["MasterFilename"] = master_filename
				property_file.save()
				return File(master_filename)
			else:
				# no master file chosen
				return None

	def issue(self, issue):
		# see IIssueHandler.issue

		local = (issue.file == self._file)

		self._issue_view.append_issue(issue, local)

		if issue.file == self._file:
			if issue.severity == Issue.SEVERITY_ERROR:
				self.create_marker("latex-error", issue.start, issue.end)
			elif issue.severity == Issue.SEVERITY_WARNING:
				self.create_marker("latex-warning", issue.start, issue.end)

	#
	# spell checking begin
	#
	# TODO: put this in a SpellCheckDelegate or so
	#

	def spell_check(self):
		"""
		Run a spell check on the file

		@raise ImportError: if pyenchant is not installed
		"""
		self.remove_markers("latex-spell")
		self.__word_markers = {}

		#
		# FIXME: it makes no sense to pass _document here because it contains
		# the expanded model of the document. We must keep the the not expanded
		# one, too.
		#
		try:
			self.__spell_checker.run(self._document, self.edited_file, self)
		except ImportError:
			open_error("Enchant is missing", "The enchant library and its Python bindings (package is propably called pyenchant) are needed for spell checking but could not be found")

	def on_misspelled_word(self, word, position):
		# see IMisspelledWordHandler.on_misspelled_word
		marker = self.create_marker("latex-spell", position, position + len(word))
		self.__word_markers[marker.id] = word

	def on_marker_activated(self, marker, event):
		"""
		A marker has been activated
		"""
		#self._log.debug("activate_marker(%s, %s)" % (marker, event))

		if marker.type == "latex-spell":
			word = self.__word_markers[marker.id]
			suggestions = self.__spell_checker.find_suggestions(word)

			self._log.debug(str(suggestions))

			# build and show the context menu
			menu = self.__get_suggestions_menu(word, suggestions, marker)
			menu.popup(None, None, None, event.button, event.time)

			# swallow the signal so that the original context menu
			# isn't shown
			return True

	def __get_suggestions_menu(self, word, suggestions, marker):
		"""
		Return the context menu for spell check suggestions

		@param word: the misspelled word
		@param suggestions: a list of suggested words
		@param marker: the activated Marker
		"""
		suggestions_menu = gtk.Menu()

		suggestions_menu.add(gtk.SeparatorMenuItem())

		item_add = gtk.ImageMenuItem(gtk.STOCK_ADD)
		item_add.connect("activate", self.__on_add_word_activated, marker, word)
		suggestions_menu.add(item_add)

		suggestions_menu.add(gtk.SeparatorMenuItem())

		item_abort = gtk.ImageMenuItem(gtk.STOCK_CANCEL)
		item_abort.connect("activate", self.__on_abort_spell_check_activated)
		suggestions_menu.add(item_abort)

		suggestions_menu.show_all()

		# add suggestions
		suggestions.reverse()	# we insert in reverse order, so reverse before

		for suggestion in suggestions:
			if suggestion.user_defined:
				item = gtk.ImageMenuItem(suggestion.word)
				item.set_image(gtk.image_new_from_stock(gtk.STOCK_FLOPPY, gtk.ICON_SIZE_MENU))
			else:
				item = gtk.MenuItem(suggestion.word)

			item.connect("activate", self.__on_suggestion_activated, suggestion, marker)
			suggestions_menu.insert(item, 0)
			item.show()

		return suggestions_menu

	def __on_suggestion_activated(self, menu_item, suggestion, marker):
		"""
		A suggestion from the context menu has been activated

		@param menu_item: the activated MenuItem
		@param suggestion: the word
		"""
		self.replace_marker_content(marker, suggestion.word)

	def __on_abort_spell_check_activated(self, menu_item):
		"""
		"""
		self.remove_markers("latex-spell")

	def __on_add_word_activated(self, menu_item, marker, word):
		"""
		Add a word to the dictionary

		@param marker: the marker for the word
		@param word: the checked word
		"""
		self.__spell_checker.add_word(word)
		self.remove_marker(marker)

	#
	# spell checking end
	#

	def on_cursor_moved(self, offset):
		"""
		The cursor has moved
		"""
		if self._preferences.get_bool("ConnectOutlineToEditor", True):
			self._outline_view.select_path_by_offset(offset)

	@property
	def file(self):
		# overrides Editor.file

		# we may not call self._document.is_master because _document is always
		# replaced by the master model
		if self._document_is_master:
			return self._file
		else:
			return self.__master_file

	@property
	def edited_file(self):
		"""
		Always returns the really edited file instead of the master

		This is called by the outline view to identify foreign nodes
		"""
		return self._file

	def destroy(self):
		# stop listening preferences
		self._preferences.remove_monitor(self)

		# unreference the window context
		del self._context

		# destroy the cached document
		self._document.destroy()
		del self._document

		Editor.destroy(self)

	def __del__(self):
		self._log.debug("Properly destroyed %s" % self)
Esempio n. 15
0
class LaTeXEditor(Editor, IIssueHandler):

    extensions = Preferences().get("latex-extensions").split(",")

    dnd_extensions = [".png", ".pdf", ".bib", ".tex"]

    @property
    def completion_handlers(self):
        self.__latex_completion_handler = LaTeXCompletionHandler()
        return [self.__latex_completion_handler]

    def init(self, file, context):
        """
        @param file: base.File
        @param context: base.WindowContext
        """
        LOG.debug("init(%s)" % file)

        self._file = file
        self._context = context

        self._preferences = DocumentPreferences(self._file)
        self._preferences.connect("preferences-changed",
                                  self._on_preferences_changed)

        self.register_marker_type(
            "latex-error", self._preferences.get("error-background-color"))
        self.register_marker_type(
            "latex-warning", self._preferences.get("warning-background-color"))

        self._issue_view = context.find_view(self, "IssueView")
        self._outline_view = context.find_view(self, "LaTeXOutlineView")

        self._parser = LaTeXParser()
        self._outline_generator = LaTeXOutlineGenerator()
        self._validator = LaTeXValidator()
        self._document = None

        # if the document is no master we display an info message on the packages to
        # include - _ensured_packages holds the already mentioned packages to not
        # annoy the user
        self._ensured_packages = []

        #
        # initially parse
        #
        self._change_reference = self.initial_timestamp

        GObject.idle_add(self.__parse)
        self.__update_neighbors()

    def _on_preferences_changed(self, prefs, key, new_value):
        if key in [
                "outline-show-labels", "outline-show-tables",
                "outline-show-graphics"
        ]:
            # regenerate outline model
            if self._document_is_master:
                self._outline = self._outline_generator.generate(
                    self._document, self)
                self._outline_view.set_outline(self._outline)
            else:
                # FIXME: self._document contains the full model of child and master
                # so we may not use it for regenerating the outline here
                self.__parse()

    def drag_drop_received(self, files):
        # see base.Editor.drag_drop_received

        # TODO: we need to insert the source at the drop location - so pass it here

        LOG.debug("drag_drop: %s" % files)

#        if len(files) == 1:
#            file = files[0]
#            self._log.debug("Got one file: %s, extension: %s" % (file.path, file.extension))
#            if file.extension == ".png":
#                self._log.debug("PNG image - including...")
#                source = "\\includegraphics{%s}" % file.path
#                self.insert(source)

    def insert(self, source):
        # see base.Editor.insert

        if type(source) is LaTeXSource:
            if source.packages and len(source.packages) > 0:
                self.ensure_packages(source.packages)

            Editor.insert(self, source.source)
        else:
            Editor.insert(self, source)

    POSITION_PACKAGES, POSITION_BIBLIOGRAPHY = 1, 2

    def insert_at_position(self, source, position):
        """
        Insert source at a certain position in the LaTeX document:

         * POSITION_PACKAGES: after the last \usepackage statement
         * POSITION_BIBLIOGRAPHY: before \end{document}

        @param source: a LaTeXSource object
        @param position: POSITION_PACKAGES | POSITION_BIBLIOGRAPHY
        """

        if position == self.POSITION_BIBLIOGRAPHY:
            offset = self._document.end_of_document
            Editor.insert_at_offset(self, offset, source, True)
        elif position == self.POSITION_PACKAGES:
            offset = self._document.end_of_packages
            Editor.insert_at_offset(self, offset, source, False)
        else:
            raise NotImplementedError

    def ensure_packages(self, packages):
        """
        Ensure that certain packages are included

        @param packages: a list of package names
        """
        self.__parse()  # ensure up-to-date document model

        if not self._document_is_master:
            LOG.debug("ensure_packages: document is not a master")

            # find the packages that haven't already been mentioned
            info_packages = [
                p for p in packages if not p in self._ensured_packages
            ]

            if len(info_packages) > 0:
                # generate markup
                li_tags = "\n".join(
                    [" • <tt>%s</tt>" % p for p in info_packages])

                from ..util import open_info
                open_info(
                    "LaTeX Package Required",
                    "Please make sure that the following packages are included in the master document per <tt>\\usepackage</tt>: \n\n%s"
                    % li_tags)

                # extend the already mentioned packages
                self._ensured_packages.extend(info_packages)

            return

        # find missing packages
        present_package_names = [p.value for p in self._outline.packages]
        package_names = [p for p in packages if not p in present_package_names]

        # insert the necessary \usepackage commands
        if len(package_names) > 0:
            source = "\n" + "\n".join(
                ["\\usepackage{%s}" % n for n in package_names])
            self.insert_at_position(source, self.POSITION_PACKAGES)

    def on_save(self):
        """
        The file has been saved

        Update models
        """

        #        from multiprocessing import Process
        #
        #        p_parse = Process(target=self.__parse)
        #        p_parse.start()

        self.__parse()

    def __update_neighbors(self):
        """
        Find all files in the working directory that are relevant for LaTeX, e.g.
        other *.tex files or images.
        """

        # TODO: this is only needed to feed the LaTeXCompletionHandler. So maybe it should
        # know the edited file and the Editor should call an update() method of the handler
        # when the file is saved.

        #FIXME: Look in graphicspath here, and in subdirs.

        tex_files = self._file.find_neighbors(".tex")
        bib_files = self._file.find_neighbors(".bib")

        graphic_files = []
        for extension in [".ps", ".pdf", ".png", ".jpg", ".eps"]:
            graphic_files.extend(self._file.find_neighbors(extension))

        self.__latex_completion_handler.set_neighbors(tex_files, bib_files,
                                                      graphic_files)

    #@verbose
    def __parse(self):
        """
        Ensure that the document model is up-to-date
        """
        if self.content_changed(self._change_reference):
            # content has changed so document model may be dirty
            self._change_reference = self.current_timestamp

            LOG.debug("Parsing document...")

            # reset highlight
            self.remove_markers("latex-error")
            self.remove_markers("latex-warning")

            # reset issues
            self._issue_view.clear()

            if BENCHMARK: t = time.clock()

            # parse document
            if self._document != None:
                self._document.destroy()
                del self._document
            self._document = self._parser.parse(self.content, self._file, self)

            # update document preferences
            self._preferences.parse_content(self.content)

            if BENCHMARK:
                LOG.info("LaTeXParser.parse: %f" % (time.clock() - t))

            LOG.debug("Parsed %s bytes of content" % len(self.content))

            # FIXME: the LaTeXChooseMasterAction enabled state has to be updated on tab change, too!

            if self._document.is_master:

                self._context.set_action_enabled("LaTeXChooseMasterAction",
                                                 False)
                self._document_is_master = True

                # expand child documents
                expander = LaTeXReferenceExpander()
                expander.expand(self._document, self._file, self, self.charset)

                # generate outline from the expanded model
                self._outline = self._outline_generator.generate(
                    self._document, self)

                # pass to view
                self._outline_view.set_outline(self._outline)

                # validate
                self._validator.validate(self._document, self._outline, self,
                                         self._preferences)
            else:
                LOG.debug("Document is not a master")

                self._context.set_action_enabled("LaTeXChooseMasterAction",
                                                 True)
                self._document_is_master = False

                # the outline used by the outline view has to be created only from the child model
                # otherwise we see the outline of the master and get wrong offsets
                self._outline = self._outline_generator.generate(
                    self._document, self)
                self._outline_view.set_outline(self._outline)

                # find master
                master_file = self.__master_file

                if master_file is None:
                    return

                # parse master
                master_content = open(master_file.path).read()
                self._document = self._parser.parse(master_content,
                                                    master_file, self)

                # expand its child documents
                expander = LaTeXReferenceExpander()
                expander.expand(self._document, master_file, self,
                                self.charset)

                # create another outline of the expanded master model to make elements
                # from the master available (labels, colors, BibTeX files etc.)
                self._outline = self._outline_generator.generate(
                    self._document, self)

                # validate
                prefs = DocumentPreferences(master_file)
                prefs.parse_content(master_content)
                self._validator.validate(self._document, self._outline, self,
                                         prefs)

            # pass outline to completion
            self.__latex_completion_handler.set_outline(self._outline)

            # pass neighbor files to completion
            self.__update_neighbors()

            LOG.debug("Parsing finished")

    def choose_master_file(self):
        master_filename = ChooseMasterDialog().run(self._file.dirname)
        if master_filename:
            # relativize the master filename
            master_filename = File(master_filename).relativize(
                self._file.dirname, True)
            self._preferences.set("master-filename", master_filename)
        return master_filename

    @property
    def __master_file(self):
        """
        Find the LaTeX master of this child

        @return: base.File
        """
        path = self._preferences.get("master-filename")
        if path != None:
            if File.is_absolute(path):
                LOG.debug("master path is absolute")
                return File(path)
            else:
                LOG.debug("master path is relative")
                return File.create_from_relative_path(path, self._file.dirname)
        else:
            # master filename not found, ask user
            master_filename = self.choose_master_file()
            if master_filename:
                return File.create_from_relative_path(master_filename,
                                                      self._file.dirname)
            else:
                return None

    def issue(self, issue):
        # see IIssueHandler.issue

        local = (issue.file == self._file)

        self._issue_view.append_issue(issue, local)

        if issue.file == self._file:
            if issue.severity == Issue.SEVERITY_ERROR:
                self.create_marker("latex-error", issue.start, issue.end)
            elif issue.severity == Issue.SEVERITY_WARNING:
                self.create_marker("latex-warning", issue.start, issue.end)

    def on_cursor_moved(self, offset):
        """
        The cursor has moved
        """
        if self._preferences.get("outline-connect-to-editor"):
            self._outline_view.select_path_by_offset(offset)

    @property
    def file(self):
        # overrides Editor.file

        # we may not call self._document.is_master because _document is always
        # replaced by the master model
        if self._document_is_master:
            return self._file
        else:
            return self.__master_file

    @property
    def edited_file(self):
        """
        Always returns the really edited file instead of the master

        This is called by the outline view to identify foreign nodes
        """
        return self._file

    def destroy(self):
        # unreference the window context
        del self._context

        # destroy the cached document
        self._document.destroy()
        del self._document

        Editor.destroy(self)
Esempio n. 16
0
 def __init__(self):
     self._parser = LaTeXParser()
     self._expander = LaTeXReferenceExpander()
Esempio n. 17
0
	def __init__(self):
		self._parser = LaTeXParser()
		self._expander = LaTeXReferenceExpander()