[gedit-plugins] New session saver plugin that allows to load and save sets of previously open files



commit 0bcba8c0739936aaa85ab604a6e028bdb5b28de0
Author: Jordi Mas <jmas softcatala org>
Date:   Thu Jul 4 23:35:06 2019 +0200

    New session saver plugin that allows to load and save sets of previously open files

 help/C/session-saver.page                          |  83 ++++++++
 help/meson.build                                   |   1 +
 meson.build                                        |   1 +
 meson_options.txt                                  |   1 +
 plugins/sessionsaver/meson.build                   |  41 ++++
 .../sessionsaver/sessionsaver.plugin.desktop.in.in |  10 +
 plugins/sessionsaver/sessionsaver/__init__.py      |  26 +++
 plugins/sessionsaver/sessionsaver/appactivable.py  |  72 +++++++
 plugins/sessionsaver/sessionsaver/dialogs.py       | 197 ++++++++++++++++++
 plugins/sessionsaver/sessionsaver/store/session.py |  42 ++++
 .../sessionsaver/store/sessionstore.py             |  80 ++++++++
 .../sessionsaver/store/xmlsessionstore.py          |  95 +++++++++
 .../sessionsaver/sessionsaver/ui/sessionsaver.ui   | 220 +++++++++++++++++++++
 .../sessionsaver/sessionsaver/windowactivable.py   | 111 +++++++++++
 plugins/sessionsaver/tests/meson.build             |  23 +++
 plugins/sessionsaver/tests/test-saved-sessions.xml |  19 ++
 plugins/sessionsaver/tests/testsession.py          |  40 ++++
 plugins/sessionsaver/tests/testsessionstore.py     |  87 ++++++++
 plugins/sessionsaver/tests/testxmlsessionstore.py  |  58 ++++++
 po/POTFILES.in                                     |   5 +
 20 files changed, 1212 insertions(+)
---
diff --git a/help/C/session-saver.page b/help/C/session-saver.page
new file mode 100644
index 0000000..ec56d2c
--- /dev/null
+++ b/help/C/session-saver.page
@@ -0,0 +1,83 @@
+<page xmlns="http://projectmallard.org/1.0/";
+      xmlns:its="http://www.w3.org/2005/11/its";
+      type="topic" style="task"
+      id="plugin-session-saver">
+
+  <info>
+    <link type="guide" xref="gedit-plugin-guide#gedit-additional-plugins"/>
+    <revision version="1.0" date="2018-03-04" status="review"/>
+
+    <credit type="author">
+      <name>Jordi Mas i Hernàndez</name>
+      <email its:translate="no">jmas softcatala org</email>
+    </credit>
+
+    <include href="legal-plugins.xml" xmlns="http://www.w3.org/2001/XInclude"/>
+
+    <desc>Save and restore your working sessions</desc>
+  </info>
+
+  <title>Session Saver</title>
+
+  <p>The <app>Session Saver</app> plugin allows you to save your current open documents and open them 
again.</p>
+
+  <section id="enable-session-saver">
+    <title>Enable Session Saver Plugin</title>
+
+    <steps>
+      <title>To enable Session Saver plugin:</title>
+      <item>
+        <p>Select <guiseq><gui style="menu">gedit</gui>
+        <gui style="menuitem">Preferences</gui>
+        <gui style="tab">Plugins</gui></guiseq>.</p>
+      </item>
+      <item>
+        <p>Select <gui style="menuitem">Session Saver</gui> to enable the
+        plugin.</p>
+      </item>
+    </steps>
+
+  </section>
+
+  <section id="enable-session-saver">
+    <title>Save a session</title>
+      <p>Save all your open files to be able to open them later.</p>
+    <steps>
+      <title>To save a session:</title>
+      <item>
+        <p>Select <guiseq><gui style="menu">gedit</gui>
+        <gui style="menuitem">Tools</gui>
+        <gui style="tab">Save Session...</gui></guiseq>.</p>
+      </item>
+    </steps>
+  </section>
+
+  <section id="open-session-saver">
+    <title>Open a session</title>
+      <p>Open all the files from an already saved session.</p>
+    <steps>
+      <title>To open a session:</title>
+      <item>
+        <p>Select <guiseq><gui style="menu">gedit</gui>
+        <gui style="menuitem">Tools</gui></guiseq></p>
+      </item>
+      <item>
+        <p>Select one of the saved sessions from the menu.</p>
+      </item>
+    </steps>
+  </section>
+
+  <section id="manage-session-saver">
+    <title>Manage Sessions</title>
+      <p>Open and delete your already saved sessions</p>
+    <steps>
+      <title>Manage sessions</title>
+        <item>
+          <p>Select <guiseq><gui style="menu">gedit</gui>
+          <gui style="menuitem">Tools</gui>
+          <gui style="tab">Manage Saved Sessions...</gui></guiseq></p>
+        </item>
+    </steps>
+  </section>
+
+</page>
diff --git a/help/meson.build b/help/meson.build
index fe54ce1..bf6b967 100644
--- a/help/meson.build
+++ b/help/meson.build
@@ -10,6 +10,7 @@ gedit_help_sources = [
   'join-split-lines.page',
   'legal-plugins.xml',
   'multi-edit.page',
+  'session-saver.page',
   'terminal.page',
   'text-size.page',
   'translate.page',
diff --git a/meson.build b/meson.build
index b1c295e..6f07951 100644
--- a/meson.build
+++ b/meson.build
@@ -91,6 +91,7 @@ all_plugins = {
   'git': {'language': 'python'},
   'joinlines': {'language': 'python'},
   'multiedit': {'language': 'python'},
+  'sessionsaver': {'language': 'python'},
   'smartspaces': {'language': 'python'},
   'terminal': {'language': 'python'},
   'textsize': {'language': 'python'},
diff --git a/meson_options.txt b/meson_options.txt
index d2770b7..a7a578f 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -10,6 +10,7 @@ option('plugin_findinfiles', type: 'boolean', value: false)
 option('plugin_git', type: 'boolean')
 option('plugin_joinlines', type: 'boolean')
 option('plugin_multiedit', type: 'boolean')
+option('plugin_sessionsaver', type: 'boolean')
 option('plugin_smartspaces', type: 'boolean')
 option('plugin_terminal', type: 'boolean')
 option('plugin_textsize', type: 'boolean')
diff --git a/plugins/sessionsaver/meson.build b/plugins/sessionsaver/meson.build
new file mode 100644
index 0000000..927715e
--- /dev/null
+++ b/plugins/sessionsaver/meson.build
@@ -0,0 +1,41 @@
+install_subdir(
+  'sessionsaver',
+  exclude_directories: ['ui'],
+  install_dir: join_paths(
+    pkglibdir,
+    'plugins',
+  )
+)
+
+install_data(
+  'sessionsaver/ui/sessionsaver.ui',
+  install_dir: join_paths(
+    pkgdatadir,
+    'plugins',
+    'sessionsaver',
+    'ui',
+  )
+)
+
+subdir('tests')
+
+sessionsaver_plugin_in = configure_file(
+  input: 'sessionsaver.plugin.desktop.in.in',
+  output: 'sessionsaver.plugin.desktop.in',
+  configuration: plugin_in,
+  install: false,
+)
+
+sessionsaver_plugin = custom_target(
+  'sessionsaver.plugin',
+  input: sessionsaver_plugin_in,
+  output: 'sessionsaver.plugin',
+  command: msgfmt_plugin_cmd,
+  install: true,
+  install_dir: join_paths(
+    pkglibdir,
+    'plugins',
+  )
+)
+
+
diff --git a/plugins/sessionsaver/sessionsaver.plugin.desktop.in.in 
b/plugins/sessionsaver/sessionsaver.plugin.desktop.in.in
new file mode 100644
index 0000000..04829a2
--- /dev/null
+++ b/plugins/sessionsaver/sessionsaver.plugin.desktop.in.in
@@ -0,0 +1,10 @@
+[Plugin]
+Loader=python3
+Module=sessionsaver
+IAge=3
+Name=Session Saver
+Description=Save and restore your working sessions
+Authors=Steve Frécinaux;Jordi Mas i Hernàndez
+Copyright=Copyright © 2006 Steve Frécinaux;Copyright © 2019 Jordi Mas i Hernàndez
+Website=http://www.gedit.org
+Version=@VERSION@
diff --git a/plugins/sessionsaver/sessionsaver/__init__.py b/plugins/sessionsaver/sessionsaver/__init__.py
new file mode 100644
index 0000000..790a3ef
--- /dev/null
+++ b/plugins/sessionsaver/sessionsaver/__init__.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+#
+#  Copyrignt (C) 2019 Jordi Mas <jmas softcatala org>
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program 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 General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 51 Franklin Street, Fifth Floor,
+#  Boston, MA 02110-1301, USA.
+
+import gi
+gi.require_version('Gtk', '3.0')
+gi.require_version('GtkSource', '4')
+gi.require_version('PeasGtk', '1.0')
+
+from .appactivable import SessionSaverAppActivatable
+from .windowactivable import SessionSaverWindowActivatable
diff --git a/plugins/sessionsaver/sessionsaver/appactivable.py 
b/plugins/sessionsaver/sessionsaver/appactivable.py
new file mode 100644
index 0000000..f271c40
--- /dev/null
+++ b/plugins/sessionsaver/sessionsaver/appactivable.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+#
+#  Copyrignt (C) 2019 Jordi Mas <jmas softcatala org>
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program 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 General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 51 Franklin Street, Fifth Floor,
+#  Boston, MA 02110-1301, USA.
+
+from gi.repository import GObject, Gio, Gedit
+from .store.xmlsessionstore import XMLSessionStore
+
+try:
+    import gettext
+    gettext.bindtextdomain('gedit-plugins')
+    gettext.textdomain('gedit-plugins')
+    _ = gettext.gettext
+except:
+    _ = lambda s: s
+
+
+
+class SessionSaverAppActivatable(GObject.Object, Gedit.AppActivatable):
+
+    app = GObject.Property(type=Gedit.App)
+    __instance = None
+
+    def __init__(self):
+        GObject.Object.__init__(self)
+        SessionSaverAppActivatable.__instance = self
+
+    @classmethod
+    def get_instance(cls):
+        return cls.__instance
+
+    def do_activate(self):
+        self._insert_session_menu()
+
+    def do_deactivate(self):
+        self.menu_ext = None
+
+    def _insert_session_menu(self):
+        self.menu_ext = self.extend_menu("tools-section")
+
+        item = Gio.MenuItem.new(_("_Manage Saved Sessions..."), "win.managedsession")
+        self.menu_ext.append_menu_item(item)
+
+        item = Gio.MenuItem.new(_("_Save Session..."), "win.savesession")
+        self.menu_ext.append_menu_item(item)
+
+        self.sessions = XMLSessionStore()
+        for i, session in enumerate(self.sessions):
+            session_id = 'win.session_{0}'.format(i)
+            item = Gio.MenuItem.new(_("Recover '{0}' Session").format(session.name), session_id)
+            self.menu_ext.append_menu_item(item)
+
+    def _remove_session_menu(self):
+        self.menu_ext.remove_items()
+
+    def update_session_menu(self):
+        self._remove_session_menu()
+        self._insert_session_menu()
diff --git a/plugins/sessionsaver/sessionsaver/dialogs.py b/plugins/sessionsaver/sessionsaver/dialogs.py
new file mode 100644
index 0000000..f8b80ee
--- /dev/null
+++ b/plugins/sessionsaver/sessionsaver/dialogs.py
@@ -0,0 +1,197 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2007 - Steve Frécinaux <code istique net>
+# Copyright (c) 2010 - Kenny Meyer <knny myer gmail com>
+# Licence: GPL2 or later
+
+from gi.repository import GObject, Gtk, Gedit
+import os.path
+from gpdefs import GETTEXT_PACKAGE
+from .store.session import Session
+
+
+try:
+    import gettext
+    gettext.bindtextdomain('gedit-plugins')
+    gettext.textdomain('gedit-plugins')
+    _ = gettext.gettext
+except:
+    _ = lambda s: s
+
+class SessionModel(Gtk.ListStore):
+    OBJECT_COLUMN = 0
+    NAME_COLUMN = 1
+    N_COLUMNS = 2
+
+    def __init__(self, store):
+        super(SessionModel, self).__init__(GObject.TYPE_PYOBJECT, str)
+        self.store = store
+        for session in store:
+            row = { self.OBJECT_COLUMN : session,
+                    self.NAME_COLUMN: session.name }
+            self.append(row.values())
+        self.store.connect_after('session-added', self.on_session_added)
+        self.store.connect('session-removed', self.on_session_removed)
+
+    def on_session_added(self, store, session):
+        row = { self.OBJECT_COLUMN : session,
+                self.NAME_COLUMN: session.name }
+        self.append(row.values())
+
+    def on_session_removed(self, store, session):
+        it = self.get_iter_first()
+        if it is not None:
+            while True:
+                stored_session = self.get_value(it, self.OBJECT_COLUMN)
+                if stored_session == session:
+                    self.remove(it)
+                    break
+                it = self.iter_next(it)
+                if not it:
+                    break
+
+class Dialog(object):
+    UI_FILE = "sessionsaver.ui"
+
+    def __init__(self, main_widget, datadir, parent_window = None):
+        super(Dialog, self).__init__()
+
+        if parent_window is None:
+            parent_window = Gedit.App.get_default().get_active_window()
+        self.parent = parent_window
+
+        self.ui = Gtk.Builder()
+        self.ui.set_translation_domain(GETTEXT_PACKAGE)
+
+        self.ui.add_from_file(os.path.join(datadir, 'ui', self.UI_FILE))
+        self.dialog = self.ui.get_object(main_widget)
+        self.dialog.connect('delete-event', self.on_delete_event)
+
+    def __getitem__(self, item):
+        return self.ui.get_object(item)
+
+    def on_delete_event(self, dialog, event):
+        dialog.hide()
+        return True
+
+    def __del__(self):
+        self.__class__._instance = None
+
+    def run(self):
+        self.dialog.set_transient_for(self.parent)
+        self.dialog.show()
+
+    def destroy(self):
+        self.dialog.destroy()
+        self.__del__()
+
+class SaveSessionDialog(Dialog):
+    def __init__(self, window, sessions, current_session, on_updated_sessions, data_dir):
+        super(SaveSessionDialog, self).__init__('save-session-dialog',
+                                                data_dir,
+                                                window)
+
+        self.NAME_COLUMN = 1
+        self.on_updated_sessions = on_updated_sessions
+        self.sessions = sessions
+
+        model = SessionModel(sessions)
+
+        self.combobox = self['session-name']
+        self.combobox.set_model(model)
+        self.combobox.set_entry_text_column(self.NAME_COLUMN)
+        self.combobox.connect("changed", self.on_name_combo_changed)
+
+        if current_session is None:
+            self.on_name_combo_changed(self.combobox)
+        else:
+            self._set_combobox_active_by_name(current_session)
+
+        self.dialog.connect('response', self.on_response)
+
+    def _set_combobox_active_by_name(self, option_name):
+        model = self.combobox.get_model()
+        piter = model.get_iter_first()
+        while piter is not None:
+            if model.get_value(piter, self.NAME_COLUMN) == option_name:
+                self.combobox.set_active_iter(piter)
+                return True
+            piter = model.iter_next(piter)
+        return False
+
+    def on_name_combo_changed(self, combo):
+        name = combo.get_child().get_text()
+        self['save_button'].set_sensitive(len(name) > 0)
+
+    def on_response(self, dialog, response_id):
+        if response_id == Gtk.ResponseType.OK:
+            files = [doc.get_location()
+                        for doc in self.parent.get_documents()
+                        if doc.get_location() is not None]
+            name = self.combobox.get_child().get_text()
+            self.sessions.add(Session(name, files))
+            self.sessions.save()
+            self.on_updated_sessions()
+        self.destroy()
+
+class SessionManagerDialog(Dialog):
+    def __init__(self, window, on_updated_sessions, on_load_session, sessions, data_dir):
+        super(SessionManagerDialog, self).__init__('session-manager-dialog',
+                                                data_dir,
+                                                window)
+
+        self.on_updated_sessions = on_updated_sessions
+        self.on_load_session = on_load_session
+        self.sessions = sessions
+        self.sessions_updated = False
+
+        model = SessionModel(sessions)
+
+        self.view = self['session-view']
+        self.view.set_model(model)
+
+        renderer = Gtk.CellRendererText()
+        column = Gtk.TreeViewColumn(_("Session Name"), renderer, text = model.NAME_COLUMN)
+        self.view.append_column(column)
+
+        handlers = {
+            'on_close_button_clicked': self.on_close_button_clicked,
+            'on_open_button_clicked': self.on_open_button_clicked,
+            'on_delete_button_clicked': self.on_delete_button_clicked
+        }
+        self.ui.connect_signals(handlers)
+
+    def on_delete_event(self, dialog, event):
+        dialog.hide()
+        self._should_save_sessions()
+        return True
+
+    def get_current_session(self):
+        (model, selected) = self.view.get_selection().get_selected()
+        if selected is None:
+            return None
+        return model.get_value(selected, SessionModel.OBJECT_COLUMN)
+
+    def on_open_button_clicked(self, button):
+        session = self.get_current_session()
+        if session is not None:
+            self.on_load_session(session)
+
+    def on_delete_button_clicked(self, button):
+        session = self.get_current_session()
+        self.sessions.remove(session)
+        self.sessions_updated = True
+
+    def _should_save_sessions(self):
+        if self.sessions_updated == False:
+            return
+
+        self.sessions.save()
+        self.on_updated_sessions()
+        self.sessions_updated = False
+
+    def on_close_button_clicked(self, button):
+        self._should_save_sessions()
+        self.destroy()
+
+
+# ex:ts=4:et:
diff --git a/plugins/sessionsaver/sessionsaver/store/session.py 
b/plugins/sessionsaver/sessionsaver/store/session.py
new file mode 100644
index 0000000..bcbd56b
--- /dev/null
+++ b/plugins/sessionsaver/sessionsaver/store/session.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# store.py
+# This file is part of gedit Session Saver Plugin
+#
+# Copyright (C) 2006-2007 - Steve Frécinaux <code istique net>
+# Copyright (C) 2010 - Kenny Meyer <knny myer gmail com>
+#
+# gedit Session Saver Plugin is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# gedit Session Saver Plugin 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gedit Session Saver Plugin; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor,
+# Boston, MA  02110-1301  USA
+
+from gi.repository import Gio
+
+class Session(object):
+    def __init__(self, name, files = None):
+        super(Session, self).__init__()
+        self.name = name
+        if files is None:
+            files = []
+        self.files = files
+
+    def __lt__(self, session):
+        return (self.name.lower() < session.name.lower())
+
+    def __eq__(self, session):
+        return (self.name.lower() == session.name.lower())
+
+    def add_file(self, filename):
+        self.files.append(Gio.file_new_for_uri(filename))
+
+# ex:ts=4:et:
diff --git a/plugins/sessionsaver/sessionsaver/store/sessionstore.py 
b/plugins/sessionsaver/sessionsaver/store/sessionstore.py
new file mode 100644
index 0000000..dd80a9a
--- /dev/null
+++ b/plugins/sessionsaver/sessionsaver/store/sessionstore.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+# store.py
+# This file is part of gedit Session Saver Plugin
+#
+# Copyright (C) 2006-2007 - Steve Frécinaux <code istique net>
+# Copyright (C) 2010 - Kenny Meyer <knny myer gmail com>
+#
+# gedit Session Saver Plugin is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# gedit Session Saver Plugin 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gedit Session Saver Plugin; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor,
+# Boston, MA  02110-1301  USA
+
+import os.path
+from gi.repository import GObject, GLib, Gio
+from .session import Session
+
+class SessionStore(GObject.Object):
+    __gsignals__ = {
+        "session-added":    (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE,
+                             (GObject.TYPE_PYOBJECT,)),
+        "session-changed":  (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE,
+                             (GObject.TYPE_PYOBJECT,)),
+        "session-removed":  (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE,
+                            (GObject.TYPE_PYOBJECT,))
+    }
+
+    def __init__(self):
+        super(SessionStore, self).__init__()
+        self._sessions = []
+
+    def __iter__(self):
+        return iter(self._sessions)
+
+    def __getitem__(self, index):
+        return self._sessions[index]
+
+    def __getslice__(self, i, j):
+        return self._sessions[i:j]
+
+    def __len__(self):
+        return len(self._sessions)
+
+    def do_session_added(self, session):
+        self._sessions.append(session)
+        self._sessions.sort()
+
+    def do_session_changed(self, session):
+        index = self._sessions.index(session)
+        self._sessions[index] = session
+
+    def add(self, session):
+        assert isinstance(session, Session)
+
+        if session in self:
+            self.emit('session-changed', session)
+        else:
+            self.emit('session-added', session)
+
+    def do_session_removed(self, session):
+        self._sessions.remove(session)
+
+    def remove(self, session):
+        assert isinstance(session, Session)
+        if session in self:
+            self.emit('session-removed', session)
+
+    def index(self, session):
+        return self._sessions.index(session)
+
+# ex:ts=4:et:
diff --git a/plugins/sessionsaver/sessionsaver/store/xmlsessionstore.py 
b/plugins/sessionsaver/sessionsaver/store/xmlsessionstore.py
new file mode 100644
index 0000000..2b824f4
--- /dev/null
+++ b/plugins/sessionsaver/sessionsaver/store/xmlsessionstore.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+# store.py
+# This file is part of gedit Session Saver Plugin
+#
+# Copyright (C) 2006-2007 - Steve Frécinaux <code istique net>
+# Copyright (C) 2010 - Kenny Meyer <knny myer gmail com>
+#
+# gedit Session Saver Plugin is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# gedit Session Saver Plugin 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gedit Session Saver Plugin; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor,
+# Boston, MA  02110-1301  USA
+
+import os.path
+from xml.parsers import expat
+from gi.repository import GLib
+from .sessionstore import SessionStore
+from .session import Session
+
+class XMLSessionStore(SessionStore):
+    def __init__(self):
+        super(XMLSessionStore, self).__init__()
+        self.filename = os.path.join(GLib.get_user_config_dir(), 'gedit/saved-sessions.xml')
+        self.load()
+
+    def __init__(self, filename):
+        super(XMLSessionStore, self).__init__()
+        self.filename = filename
+        self.load()
+
+    def _escape(self, string):
+        return string.replace('&', '&amp;') \
+                     .replace('<', '&lt;')  \
+                     .replace('>', '&gt;')  \
+                     .replace('"', '&quot;')
+
+    def _dump_session(self, session):
+        files = ''.join(['  <file path="%s"/>\n' % self._escape(location.get_uri())
+                            for location in session.files])
+        session_name = self._escape(str(session.name))
+        return '<session name="%s">\n%s</session>\n' % (session_name, files)
+
+    def dump(self):
+        dump = [self._dump_session(session) for session in self]
+        return '<saved-sessions>\n%s</saved-sessions>\n' % ''.join(dump)
+
+    def save(self):
+        dirname = os.path.dirname(self.filename)
+        if not os.path.isdir(dirname):
+            os.makedirs(dirname)
+
+        fp = open(self.filename, "wb")
+        fp.write(bytes('<?xml version="1.0" encoding="UTF-8"?>\n','UTF-8'))
+        fp.write(bytes(self.dump(),'UTF-8'))
+        fp.close()
+
+    def load(self):
+        if not os.path.isfile(self.filename):
+            return
+
+        parser = expat.ParserCreate('UTF-8')
+        parser.buffer_text = True
+        parser.StartElementHandler = self._expat_start_handler
+        parser.EndElementHandler = self._expat_end_handler
+
+        self._current_session = None
+        try:
+            parser.ParseFile(open(self.filename, 'rb'))
+        except:
+            return
+        del self._current_session
+
+    def _expat_start_handler(self, tag, attr):
+        if tag == 'file':
+            assert self._current_session is not None
+            self._current_session.add_file(str(attr['path']))
+        elif tag == 'session':
+            assert self._current_session is None
+            self._current_session = Session(attr['name'])
+
+    def _expat_end_handler(self, tag):
+        if tag == 'session':
+            self.add(self._current_session)
+            self._current_session = None
+
+# ex:ts=4:et:
diff --git a/plugins/sessionsaver/sessionsaver/ui/sessionsaver.ui 
b/plugins/sessionsaver/sessionsaver/ui/sessionsaver.ui
new file mode 100644
index 0000000..882066a
--- /dev/null
+++ b/plugins/sessionsaver/sessionsaver/ui/sessionsaver.ui
@@ -0,0 +1,220 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+  <requires lib="gtk+" version="3.0"/>
+  <object class="GtkDialog" id="save-session-dialog">
+    <property name="can_focus">False</property>
+    <property name="border_width">6</property>
+    <property name="title" translatable="yes">Save session</property>
+    <property name="resizable">False</property>
+    <property name="type_hint">dialog</property>
+    <child>
+      <placeholder/>
+    </child>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | 
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="button2">
+                <property name="label">gtk-cancel</property>
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="receives_default">False</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="save_button">
+                <property name="label">gtk-save</property>
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="can_default">True</property>
+                <property name="has_default">True</property>
+                <property name="receives_default">False</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="vbox1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="border_width">6</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkLabel" id="label1">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Session name:</property>
+                <property name="xalign">0</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkComboBox" id="session-name">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="has_entry">True</property>
+                <child internal-child="entry">
+                  <object class="GtkEntry" id="combobox-entry2">
+                    <property name="can_focus">True</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">button2</action-widget>
+      <action-widget response="-5">save_button</action-widget>
+    </action-widgets>
+  </object>
+  <object class="GtkWindow" id="session-manager-dialog">
+    <property name="width_request">400</property>
+    <property name="height_request">200</property>
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">Saved Sessions</property>
+    <property name="type_hint">dialog</property>
+    <child>
+      <placeholder/>
+    </child>
+    <child>
+      <object class="GtkBox" id="hbox1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="border_width">6</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkScrolledWindow" id="scrolledwindow1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="hexpand">True</property>
+            <property name="hscrollbar_policy">never</property>
+            <property name="shadow_type">in</property>
+            <child>
+              <object class="GtkTreeView" id="session-view">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="headers_visible">False</property>
+                <child internal-child="selection">
+                  <object class="GtkTreeSelection" id="treeview-selection1"/>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkVButtonBox" id="vbuttonbox1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">6</property>
+            <property name="layout_style">start</property>
+            <child>
+              <object class="GtkButton" id="open-button">
+                <property name="label">gtk-open</property>
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="receives_default">False</property>
+                <property name="use_stock">True</property>
+                <signal name="clicked" handler="on_open_button_clicked" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="delete-button">
+                <property name="label">gtk-delete</property>
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="receives_default">False</property>
+                <property name="use_stock">True</property>
+                <signal name="clicked" handler="on_delete_button_clicked" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="close-button">
+                <property name="label">gtk-close</property>
+                <property name="use_action_appearance">False</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="receives_default">False</property>
+                <property name="use_stock">True</property>
+                <signal name="clicked" handler="on_close_button_clicked" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+                <property name="secondary">True</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/plugins/sessionsaver/sessionsaver/windowactivable.py 
b/plugins/sessionsaver/sessionsaver/windowactivable.py
new file mode 100644
index 0000000..60f8469
--- /dev/null
+++ b/plugins/sessionsaver/sessionsaver/windowactivable.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+#
+#  Copyrignt (C) 2019 Jordi Mas <jmas softcatala org>
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program 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 General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 51 Franklin Street, Fifth Floor,
+#  Boston, MA 02110-1301, USA.
+
+from gi.repository import GObject, Gio, Gedit
+from .dialogs import SaveSessionDialog, SessionManagerDialog
+from .store.xmlsessionstore import XMLSessionStore
+from .appactivable import SessionSaverAppActivatable
+
+try:
+    import gettext
+    gettext.bindtextdomain('gedit-plugins')
+    gettext.textdomain('gedit-plugins')
+    _ = gettext.gettext
+except:
+    _ = lambda s: s
+
+
+class SessionSaverWindowActivatable(GObject.Object, Gedit.WindowActivatable):
+
+    __gtype_name__ = "SessionSaverWindowActivatable"
+    window = GObject.Property(type=Gedit.Window)
+
+    def __init__(self):
+        GObject.Object.__init__(self)
+        self.sessions = XMLSessionStore()
+        self.n_sessions = 0
+        self.current_session = None
+
+    def do_activate(self):
+        self._insert_menus()
+
+    def _insert_menus(self):
+        action = Gio.SimpleAction(name="managedsession")
+        action.connect('activate', lambda a, p: self._on_manage_sessions_action())
+        self.window.add_action(action)
+
+        action = Gio.SimpleAction(name="savesession")
+        action.connect('activate', lambda a, p: self._on_save_session_action())
+        self.window.add_action(action)
+
+        self.sessions = XMLSessionStore()
+        self.n_sessions = len(self.sessions)
+        for i, session in enumerate(self.sessions):
+            session_id = 'session_{0}'.format(i)
+            action = Gio.SimpleAction(name=session_id)
+            action.connect('activate', self._session_menu_action, session)
+            self.window.add_action(action)
+
+    def _remove_menus(self):
+        self.window.remove_action("managedsession")
+        self.window.remove_action("savesession")
+
+        for i in range(self.n_sessions):
+            session_id = 'session_{0}'.format(i)
+            self.window.remove_action(session_id)
+
+    def _session_menu_action(self, action, parameter, session):
+        self.load_session(session)
+
+    def do_deactivate(self):
+        self._remove_menus()
+        return
+
+    def do_update_state(self):
+        return
+
+    def _on_manage_sessions_action(self):
+        data_dir = SessionSaverAppActivatable.get_instance().plugin_info.get_data_dir()
+        dialog = SessionManagerDialog(self.window, self.on_updated_sessions, self.load_session, 
self.sessions, data_dir)
+        dialog.run()
+
+    def _on_save_session_action(self):
+        data_dir = SessionSaverAppActivatable.get_instance().plugin_info.get_data_dir()
+        dialog = SaveSessionDialog(self.window, self.sessions, self.current_session, 
self.on_updated_sessions, data_dir)
+        dialog.run()
+
+    def on_updated_sessions(self):
+        SessionSaverAppActivatable.get_instance().update_session_menu()
+        self._remove_menus()
+        self._insert_menus()
+
+    def load_session(self, session):
+        # Note: a session has to stand on its own window.
+        tab = self.window.get_active_tab()
+        if tab is not None and \
+           not (tab.get_document().is_untouched() and
+                tab.get_state() == Gedit.TabState.STATE_NORMAL):
+            # Create a new gedit window
+            window = Gedit.App.get_default().create_window(None)
+            window.show()
+        else:
+            window = self.window
+
+        Gedit.commands_load_locations(window, session.files, None, 0, 0)
+        self.current_session = session.name
diff --git a/plugins/sessionsaver/tests/meson.build b/plugins/sessionsaver/tests/meson.build
new file mode 100644
index 0000000..eaf7da6
--- /dev/null
+++ b/plugins/sessionsaver/tests/meson.build
@@ -0,0 +1,23 @@
+sessionsaver_tests = {
+  'session': files('testsession.py'),
+  'sessionstore': files('testsessionstore.py'),
+  'xmlsessionstore': files('testxmlsessionstore.py'),
+}
+
+sessionsaver_srcdir = join_paths(
+  srcdir,
+  'plugins',
+  'sessionsaver',
+  'sessionsaver',
+)
+
+foreach test_name, test_script : sessionsaver_tests
+  test(
+    'test-sessionsaver-@0@'.format(test_name),
+    python3,
+    args: [test_script],
+    env: [
+      'PYTHONPATH=@0@'.format(sessionsaver_srcdir),
+    ]
+  )
+endforeach
diff --git a/plugins/sessionsaver/tests/test-saved-sessions.xml 
b/plugins/sessionsaver/tests/test-saved-sessions.xml
new file mode 100644
index 0000000..4be2aae
--- /dev/null
+++ b/plugins/sessionsaver/tests/test-saved-sessions.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<saved-sessions>
+<session name="files">
+  <file path="file:///xmlsessionstore.py"/>
+</session>
+<session name="gspell">
+  <file path="file:///home/jordi/dev/gedit/gspell/gspell/gspellregion.c"/>
+  <file path="file:///home/jordi/dev/gedit/gspell/gspell/gspell-text-view.c"/>
+  <file path="file:///home/jordi/dev/gedit/gspell/gspell/gspell-text-buffer.c"/>
+  <file path="file:///home/jordi/dev/gedit/gspell/gspell/gspell-navigator.c"/>
+  <file path="file:///home/jordi/dev/gedit/gspell/gspell/gspell-language-chooser.c"/>
+  <file path="file:///home/jordi/dev/gedit/gspell/gspell/gspell-language-chooser-dialog.c"/>
+  <file path="file:///home/jordi/dev/gedit/gspell/gspell/gspell-language-chooser-button.c"/>
+</session>
+<session name="window-activable">
+  <file path="file:///home/jordi/dev/gedit/gedit-plugins/plugins/sessionsaver/meson.build"/>
+  <file 
path="file:///home/jordi/dev/gedit/gedit-plugins/plugins/sessionsaver/sessionsaver/windowactivable.py"/>
+</session>
+</saved-sessions>
diff --git a/plugins/sessionsaver/tests/testsession.py b/plugins/sessionsaver/tests/testsession.py
new file mode 100644
index 0000000..e3cad66
--- /dev/null
+++ b/plugins/sessionsaver/tests/testsession.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2019 Jordi Mas i Hernandez <jmas softcatala org>
+#
+# This program 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 program 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 program; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+
+import unittest
+from store.session import Session
+
+class TestSession(unittest.TestCase):
+
+    def test_add_file(self):
+        session = Session("session_A")
+        session.add_file('file')
+        self.assertEqual(1, len(session.files))
+
+    def test_compare_objects(self):
+        session_a = Session("session_A")
+        session_b = Session("session_B")
+        self.assertTrue(session_a < session_b)
+        self.assertTrue(session_b > session_a)
+        self.assertTrue(session_a != session_b)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/plugins/sessionsaver/tests/testsessionstore.py b/plugins/sessionsaver/tests/testsessionstore.py
new file mode 100644
index 0000000..06ec32f
--- /dev/null
+++ b/plugins/sessionsaver/tests/testsessionstore.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2019 Jordi Mas i Hernandez <jmas softcatala org>
+#
+# This program 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 program 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 program; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+
+import unittest
+from store.session import Session
+from store.sessionstore import SessionStore
+
+class TestSessionStore(unittest.TestCase):
+
+    def test_new_two_objects(self):
+        session_a = Session("session_A")
+        store_a = SessionStore()
+        store_a.add(session_a)
+
+        session_b = Session("session_B")
+        session_c = Session("session_C")
+        store_b = SessionStore()
+        store_b.add(session_b)
+        store_b.add(session_c)
+
+        self.assertEqual(1, len(store_a))
+        self.assertEqual(2, len(store_b))
+
+    def _on_session_added(self, store, session):
+        self.added_called = True
+
+    def test_add(self):
+        self.added_called = False
+        session = Session("session_A")
+        store = SessionStore()
+        store.connect_after('session-added', self._on_session_added)
+        store.add(session)
+
+        self.assertEqual(1, len(store))
+        self.assertTrue(self.added_called)
+
+    def _on_session_updated(self, store, session):
+        self.updated_called = True
+
+
+    def test_add_same_object_update(self):
+        self.updated_called = False
+        session = Session("session_A")
+        store = SessionStore()
+        store.connect_after('session-added', self._on_session_updated)
+        store.add(session)
+        session.name = 'Session B'
+        store.add(session)
+
+        self.assertEqual(1, len(store))
+        self.assertEqual('Session B', store[0].name)
+        self.assertTrue(self.updated_called)
+
+    def test_add_equal_object(self):
+        session_a = Session("session_A")
+        session_b = Session("session_A")
+        store = SessionStore()
+        store.add(session_a)
+        store.add(session_b)
+        self.assertEqual(1, len(store))
+
+    def test_remove(self):
+        session = Session("session_A")
+        store = SessionStore()
+        store.add(session)
+        store.remove(session)
+        self.assertEqual(0, len(store))
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/plugins/sessionsaver/tests/testxmlsessionstore.py 
b/plugins/sessionsaver/tests/testxmlsessionstore.py
new file mode 100644
index 0000000..48d088f
--- /dev/null
+++ b/plugins/sessionsaver/tests/testxmlsessionstore.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2019 Jordi Mas i Hernandez <jmas softcatala org>
+#
+# This program 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 program 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 program; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+
+import unittest
+import tempfile
+from store.session import Session
+from store.xmlsessionstore import XMLSessionStore
+from os import path
+
+class TestXmlSessionStore(unittest.TestCase):
+
+    def test_load(self):
+
+        store_file = path.dirname(path.realpath(__file__))
+        store_file += '/test-saved-sessions.xml'
+        print(store_file)
+
+        store = XMLSessionStore(store_file)
+        self.assertEqual(3, len(store))
+        self.assertEqual('files', store[0].name)
+        self.assertEqual('gspell', store[1].name)
+        self.assertEqual('window-activable', store[2].name)
+
+        self.assertEqual(1, len(store[0].files))
+        self.assertEqual("/xmlsessionstore.py", store[0].files[0].get_path())
+
+    def test_save(self):
+        session_name = "session_A"
+        tmpfile = tempfile.NamedTemporaryFile()
+        store = XMLSessionStore(tmpfile.name)
+        session = Session(session_name)
+        store.add(session)
+        store.save()
+
+        store = XMLSessionStore(tmpfile.name)
+        self.assertEqual(1, len(store))
+        self.assertEqual(session_name, store[0].name)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0f4a3ae..dca1ae5 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -42,6 +42,11 @@ plugins/multiedit/gedit-multiedit.metainfo.xml.in
 plugins/multiedit/multiedit/appactivatable.py
 plugins/multiedit/multiedit.plugin.desktop.in.in
 plugins/multiedit/multiedit/viewactivatable.py
+plugins/sessionsaver/sessionsaver/appactivable.py
+plugins/sessionsaver/sessionsaver/dialogs.py
+plugins/sessionsaver/sessionsaver.plugin.desktop.in.in
+plugins/sessionsaver/sessionsaver/ui/sessionsaver.ui
+plugins/sessionsaver/sessionsaver/windowactivable.py
 plugins/smartspaces/gedit-smartspaces.metainfo.xml.in
 plugins/smartspaces/smartspaces.plugin.desktop.in.in
 plugins/terminal/gedit-terminal.metainfo.xml.in


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]