Scaffold API proposal 0.2

Gustavo Giráldez <[email protected]>
Newsgroups gmane.comp.gnome.devtools
Message-ID <[email protected]>
Hi guys,

Here's a new version of my API proposal for Scaffold with changes due to
the feedback I got from you.  Attached is the html version.  You can get
the Docbook file from
http://webs.uolsinectis.com.ar/gustavog1/scaffold/.

There are still a bunch of missing/incorrect parts.  Most notably, the
ideas from John's last mail[1]: the layout switch, highlighting and the
markers interface.  Those are particularly hard and I haven't had much
time to think about them.

I think I'll start throwing out some code, so I'd like to get the basic
interfaces first (ValueContainer, EventBus, ShellUI and UIActionTarget).

Also, I want to have a document describing the expected implementation
and behavior of the fundamental plugins (namely project manager,
document manager and session).  I'll write a draft document in the next
few days (unless somebody else wants to do it ;-)

Comments, ideas, flames? :-)  All feedback appreciated.

Regards,
Gustavo


[1] http://mail.gnome.org/archives/gnome-devtools/2004-January/msg00031.html
scaffold-api-0.2.html (text/html, 25.2 KB)
<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>Scaffold API Proposal</title><meta name="generator" content="DocBook XSL Stylesheets V1.61.2"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="article" lang="en"><div class="titlepage"><div><div><h1 class="title"><a name="scaffold-api"></a>Scaffold API Proposal</h1></div><div><p class="releaseinfo">Version 0.2</p></div><div><div class="revhistory"><table border="1" width="100%" summary="Revision history"><tr><th align="left" valign="top" colspan="2"><b>Revision History</b></th></tr><tr><td align="left">Revision 0.1</td><td align="left">Jan 5th, 2004</td></tr><tr><td align="left" colspan="2">Initial version.</td></tr><tr><td align="left">Revision 0.2</td><td align="left">Jan 17th, 2004</td></tr><tr><td align="left" colspan="2">
	  Added introduction to binary interfaces.  Value removal
	  notification.  EventBus API rename.  The UI merging
	  interface can be implemented by any plugin which would
	  support actions.  The FileOpen service should be implemented
	  by the Document Manager plugin and not the shell.  Added
	  method to create a new file to the FileOpen service.  Added
	  a description to the registered FileHandler.  Added save as,
	  close and clipboard operations to Document.  Added stock_id
	  parameter to ShellUI's add_widget.  New section about
	  runtime interfaces.  Introduced command handlers.
	</td></tr></table></div></div></div><div></div><hr></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><a href="#binary">Binary Interfaces</a></dt><dd><dl><dt><a href="#id3138592">The ValueContainer interface</a></dt><dt><a href="#id3138719">The EventBus</a></dt><dt><a href="#id3138827">The action UI merging interface UIActionTarget</a></dt><dt><a href="#id3138909">The FileOpen Service</a></dt><dt><a href="#id3138985">The Document interface</a></dt><dt><a href="#id3139019">The TextDocument interface</a></dt><dt><a href="#id3136543">ShellUI</a></dt><dt><a href="#id3136615">The data UI merging interface UIDataTarget</a></dt><dt><a href="#id3136705">BuildTarget</a></dt><dt><a href="#id3136742">Other Interfaces</a></dt><dt><a href="#id3136782">Runtime discovered interfaces</a></dt></dl></dd><dt><a href="#string">String Interfaces</a></dt><dd><dl><dt><a href="#id3136836">Value Repository Hierarchy</a></dt><dt><a href="#id3203811">Standard Events</a></dt></dl></dd><dt><a href="#startup-shutdown">Startup and Shutdown sequence</a></dt></dl></div><div class="sect1" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="binary"></a>Binary Interfaces</h2></div></div><div></div></div><p>
      In the following sections the Scaffold interfaces are presented.
      Interface here implies the <span class="type">GInterface</span> an object
      must implement to provide the functionality.  That's why the
      defined methods are minimal.  In the actual API to use these
      interfaces there will be wrappers defined when necessary for
      clarity or easy of use.
    </p><p>
      The set described below is the basic set of intefaces to the
      Scaffold IDE services.  All these will be implemented in the
      <tt class="filename">libscaffold</tt> library.  For further
      extensions two options are available:
    </p><div class="itemizedlist"><ul type="disc"><li><p>create new <span class="type">GInterface</span>s and bundle
	  them in another shared library which plugins can link
	  to</p></li><li><p>use runtime interfaces (described at the end of the
	section) which are realized as properties and signals of the
	<span class="type">GObject</span> system.</p></li></ul></div><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
	In all method declarations below the first parameter (which is
	a reference to the object implementing the interface) is
	omitted for clarity.
      </p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="id3138592"></a>The <span class="type">ValueContainer</span> interface</h3></div></div><div></div></div><p>
	One of the main components of the shell is the
	<span class="type">ValueContainer</span>. The <span class="type">ValueContainer</span>
	is an interface which can also be implemented by plugins which
	want to &#8220;<span class="quote">take over</span>&#8221; certain subpaths of the main
	hierarchy.
      </p><p>
	The shell's <span class="type">ValueContainer</span> exposes a tree
	hierarchy where each of the nodes can hold a single
	<span class="type">GValue</span>. Paths in the tree are represented by
	strings, where each element of the path is separated by a
	special character or sequence of characters (still undecided,
	but the slash '/' will be used in the following examples). The
	root of the hierarchy is not accesible, and thus the initial
	slash in a given path can be omitted. Another way of looking
	at the model exposed by <span class="type">ValueContainer</span> is as
	nested hashes.
      </p><p>
	An implementation of the <span class="type">ValueContainer</span> interface
	can support delegation. This means, if a <span class="type">GValue</span>
	which contains an object which in turn implements the
	<span class="type">ValueContainer</span> interface is installed at a given
	path, all accesses to subpaths which have it as its parent
	will be delegated to the installed object. Delegation should
	be transparent.
      </p><p>
	E.g. an object <tt class="varname">A</tt> which realizes the
	<span class="type">ValueContainer</span> interface is installed at the
	shell's path <tt class="literal">/Documents</tt>. An access to the
	path <tt class="literal">/Documents/Untitled0</tt> the shell's
	object hierarchy will be delegated to <tt class="varname">A</tt> (by
	using the <span class="type">ValueContainer</span> interface) with a path
	of <tt class="literal">/Untitled0</tt>.
      </p><p>
	There's no such thing as a relative path as in a
	filesystem. A path is an address, not a means of
	navigation.
      </p><pre class="synopsis">
  /* Value removal/replace notification callback signature. */
  typedef void (* ValueUnsetNotify) (const gchar *path, GValue *value);

  /* Sets a given value in the repository.  Returns TRUE if the value
     could be set.  If an unset notify is provided, it is called
     whenever the value is unset (i.e. either because it's being
     removed or replaced). */
  gboolean set_value (const gchar *path, 
                      GValue *value, 
                      ValueUnsetNotify unset_notify_cb,
                      GError **error);

  /* Tries to retrieve a value for a given path, returning TRUE if the
     value was set and the value itself in the value parameter. */
  gboolean get_value (const gchar *path, GValue *value, GError **error);

  /* removes a value from the repository */ 
  void unset_value (const gchar *path);

  /* gets a list of strings with the set immediate subpaths */ 
  GSList *get_children (const gchar *parent);
      </pre><p>
	The repository offered by the shell's
	<span class="type">ValueContainer</span> is the place where plugins should
	expose their services and go look for other plugins
	services. A standard hierarchy should be defined, which will
	become part of the API, along with all the interfaces.
      </p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="id3138719"></a>The <span class="type">EventBus</span></h3></div></div><div></div></div><p>
	The <span class="type">EventBus</span> interface provides a shell-wide
	notification mechanism. Events emitted in the bus are
	considered of interest of several plugins. Notifications of
	lesser importance should be implemented using the standard
	GSignal mechanism.
      </p><p>
	Group of events are selectable through patterns by the use of
	wildcards (the <tt class="function">g_pattern_spec_*</tt> API will
	be used for this).
      </p><p>
	While not enforced, it's expected that events will be
	namespaced. E.g.: <tt class="literal">Shell::ready</tt>,
	<tt class="literal">Shell::quit</tt>,
	<tt class="literal">Build::start</tt>,
	<tt class="literal">Build::stop</tt>,
	<tt class="literal">Documents::change_active</tt>, etc.. The event
	names will be part of Scaffold's API too.
      </p><p>
	Event emission is synchronous.
      </p><pre class="synopsis">
  /* registers a listener for a specific event pattern; use the returned
     id to unregister the listener */
  guint add_listener (const gchar *event_pattern, GClosure *closure);

  /* unregister a listener */
  void remove_listener (guint id);

  /* emits an event in the bus */
  void emit_event (const gchar *event);
      </pre><p>
	While presented as a separated interface, only the shell will
	implement the event bus.
      </p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="id3138827"></a>The action UI merging interface <span class="type">UIActionTarget</span></h3></div></div><div></div></div><p>
	This interface will allow a plugin to merge action-type UI
	elements (menus, toolbars). This API mirrors the
	<span class="type">GtkUIManager</span> API.
      </p><p>
	The shell will implement this interface for the application
	menu bar and toolbars, but it can also be implemented by any
	plugin which has a visual component and wants to merge actions
	for popup menus.
      </p><pre class="synopsis">
  /* install an action group */
  void add_action_group	(GtkActionGroup *group);

  /* remove an action group */ 
  void remove_action_group (GtkActionGroup *group);

  /* merge UI elements to the component's UI; the returned id is used
     for unmerging */
  guint merge_ui (const gchar *ui);

  /* unmerge a previously merged UI */
  void unmerge_ui (guint merge_id);
      </pre><p>
	The shell will have a &lt;menu&gt; element and a
	&lt;toolbar&gt; element at a minimum. Plugins implementing the
	interface will have a &lt;popup&gt; element.
      </p><div class="warning" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Warning</h3><p>
	  UNDECIDED: how to register and use several toolbars
	</p></div><div class="warning" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Warning</h3><p>
	  FIXME: add status bar API
	</p></div></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="id3138909"></a>The <span class="type">FileOpen</span> Service</h3></div></div><div></div></div><p>
	This service is one of the most important and will be
	implemented by the document manager plugin.
      </p><p>
	The concept is as follows: a plugin which can open a certain
	type of document (selected by the mimetype) registers itself
	with this interface. When an open request is made, the
	document manager checks to see if it has any registered
	provider and invokes the handler if so.  Otherwise, it might
	open the file using Gnome's standard machinery
	(<tt class="function">gnome_url_show()</tt> or whatever).
      </p><p>
	Different plugins are expected to open different types of
	documents: the glade plugin will open .glade files, the
	project manager will open .scaffold project files, etc..
      </p><pre class="synopsis">
  /* the open handler signature; if the opened document can be
     manipulated the function should return a reference to the created
     document, NULL otherwise */
  typedef Document * (* FileHandler) (const gchar *uri, 
                                      const gchar *mime_type,
                                      gboolean     read_only,
                                      GError     **error);

  /* create a new document of the given mime type */
  Document *new_file (const gchar *mime_type,
                      GError     **error);

  /* open a file and (if applicable) present it to the user; errors
     are indicated in the error parameter, and NULL is a valid result */
  Document *open_file (const gchar *uri, 
                       gboolean     read_only,
                       GError     **error);

  /* register an open handler for a mime type */
  void register_file_handler (const gchar *mime_type, 
                              const gchar *description,
                              FileHandler  handler);

  /* deregister an open handler */
  void unregister_file_handler (FileHandler handler);
      </pre></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="id3138985"></a>The <span class="type">Document</span> interface</h3></div></div><div></div></div><p>
	The document interface represents a single file opened by the
	application.
      </p><pre class="synopsis">
  /* property accessors */
  gchar *get_uri ();
  gchar *get_mime_type ();
  gboolean get_editable ();

  /* save/close operations */
  void save (gboolean save_as);
  void close ();

  /* clipboard operations */
  void copy (GtkClipboard *clipboard, gboolean cut);
  void paste (GtkClipboard *clipboard);
      </pre></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="id3139019"></a>The <span class="type">TextDocument</span> interface</h3></div></div><div></div></div><p>
	Implemented by textual documents, allows direct manipulation
	of the file contents.
      </p><p>
	Document regions are delimited by the point and the mark (ala
	emacs).  Cut and copy operations affect the region.  Insert
	occurs at the point always.
      </p><pre class="synopsis">
  /* movement modifiers: a combination of one value from the following
     mask values */

  #define RELATIVE_POS_SHIFT 0
  #define STEP_SIZE_SHIFT    2

  /* relative position */
  #define FROM_CURRENT 0 &lt;&lt; RELATIVE_POS_SHIFT
  #define FROM_START   1 &lt;&lt; RELATIVE_POS_SHIFT
  #define FROM_END     2 &lt;&lt; RELATIVE_POS_SHIFT

  /* steps */
  #define CHAR   0 &lt;&lt; STEP_SIZE_SHIFT
  #define WORD   1 &lt;&lt; STEP_SIZE_SHIFT
  #define LINE   2 &lt;&lt; STEP_SIZE_SHIFT
  #define COLUMN 3 &lt;&lt; STEP_SIZE_SHIFT

  void set_mark (gboolean swap_with_point);

  gboolean move_point (gint modifier, gint offset);

  gchar *get_text (gboolean cut);

  void insert_text (const gchar *text);

  gint get_point_position (gint modifier);
      </pre></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="id3136543"></a><span class="type">ShellUI</span></h3></div></div><div></div></div><p>
	The ShellUI interface deals with visual components in the
	shell's container (dock) and the preferences dialog.
      </p><pre class="synopsis">
  /* adds a widget to the shell's dock; returns FALSE if there already
     existed a widget with the same name */
  gboolean add_dock_widget (GtkWidget   *widget,
                            const gchar *name,
                            const gchar *title,
                            const gchar *stock_id,
                            const gchar *pos_hint);

  /* adds a preference page to the preferences dialog; returns FALSE
     if there already existed a page with the same name */
  gboolean add_preferences_widget (GtkWidget *page,
				   const gchar *name,
				   const gchar *title,
				   const gchar *category);

  /* removes a widget from the shell */
  void remove_widget (GtkWidget *widget);

  /* makes sure the widget is visible to the user */
  void present_widget (GtkWidget *widget);
      </pre><div class="warning" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Warning</h3><p>
	  UNDECIDED: The preferences dialog should be unique among the
	  application, but there might exist more than one shell at any
	  given time.  How do we deal with the preference pages?
	</p></div><div class="warning" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Warning</h3><p>
	  UNDECIDED: <tt class="function">present_widget()</tt> should open
	  the preferences dialog if the widget is a preference page?
	</p></div></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="id3136615"></a>The data UI merging interface <span class="type">UIDataTarget</span></h3></div></div><div></div></div><p>
	Plugins which have visual elements which show information that
	can be extended by other plugins (e.g. the vcs plugin can add
	information to the project manager plugin) can implement this
	interface, which is roughly a stripped down
	<span class="type">GtkCellLayout</span>.
      </p><p>
	<span class="type">GtkCellRenderers</span> can be packed in groups.  Groups
	have an id and can have a title.  The main difference with the
	<span class="type">GtkCellLayout</span> interface is the absence of a
	<span class="type">GtkTreeModel</span>/<span class="type">GtkTreeIter</span> pair to
	feed the set data function.  Instead a
	<span class="type">ValueContainer</span> and a path into it is given.
      </p><pre class="synopsis">
  /* set data function for the renderers */
  typedef void (* CellDataFunc) (CellRenderer *cell, 
	 		         ValueContainer *container, 
			         const gchar *path, 
			         gpointer user_data);

  /* create a new group of renderers to pack cells into */
  guint add_group (const gchar *group_title);

  /* pack a cell into the given group using the func to fill the data */
  void pack_cell (guint cell_group,
                  GtkCellRenderer *cell,
                  gboolean expand,
                  CellDataFunc func,
                  gpointer user_data);

  /* destroy the cell group */
  void remove_group (guint id);
      </pre><div class="warning" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Warning</h3><p>
	  I'm not really convinced with this interface, but I can't
	  find another example (besides the vcs&lt;-&gt;project
	  interaction) which would use it.
	</p></div></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="id3136705"></a><span class="type">BuildTarget</span></h3></div></div><div></div></div><p>
	The interface presented by a buildable and (possibly)
	executable project target.
      </p><div class="warning" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Warning</h3><p>
	  FIXME: this needs a lot of thought
	</p></div><pre class="synopsis">
  GList *list_sources ();

  add_source (const gchar *uri);

  remove_source (const gchar *uri);

  gboolean is_executable ();
      </pre></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="id3136742"></a>Other Interfaces</h3></div></div><div></div></div><div class="warning" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Warning</h3><p>
	  FIXME: Design the project interface
	</p></div><div class="warning" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Warning</h3><p>
	  Command handler interface: this will almost surely be
	  implemented by the shell only.
	</p></div><div class="warning" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Warning</h3><p>
	  FIXME: Other interfaces?
	</p></div></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="id3136782"></a>Runtime discovered interfaces</h3></div></div><div></div></div><p>
	Users of these runtime interfaces will be mostly plugins
	written in higher level languages (Python, C#, etc.).
      </p><p>
	To expose runtime interfaces the <span class="type">GObject</span>'s
	properties and signals will be used, so there's no need to
	defined yet another interface.
      </p><p>
	Wrappers in C (as well as in higher level languages) will be
	provided to install, access and discover
	<span class="type">GObject</span>'s properties and signals created
	specifically with the purpose of being exposed to other
	plugins.
      </p></div></div><div class="sect1" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="string"></a>String Interfaces</h2></div></div><div></div></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="id3136836"></a>Value Repository Hierarchy</h3></div></div><div></div></div><p>
	Whatever the implementation of the above interfaces, there
	must be a standard way for plugins to look for specific
	well-known interfaces.  As stated above, the value repository
	is the place for plugins to publish their services.  The
	missing part is how to name the services.
      </p><p>
	The following paths expressed from the shell's repository
	should lead to the specified services:
      </p><div class="variablelist"><dl><dt><span class="term"><tt class="literal">/Documents</tt></span></dt><dd><p>
	      Text documents collection (each child of this node
	      implements the TextDocument interface)
	    </p></dd><dt><span class="term"><tt class="literal">/Documents/Current</tt></span></dt><dd><p>
	      The currently (or last) active text document
	    </p></dd><dt><span class="term"><tt class="literal">/Project</tt></span></dt><dd><p>
	      The project interface and starting point to access all
	      project elements such as targets and sources
	    </p></dd><dt><span class="term"><tt class="literal">/Project/Targets</tt></span></dt><dd><p>
	      Targets collection
	    </p></dd><dt><span class="term"><tt class="literal">/Session</tt></span></dt><dd><p>
	      Session saved data.  This should present a
	      ValueContainer interface.  Only serializable data should
	      be allowed here.
	    </p></dd></dl></div></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="id3203811"></a>Standard Events</h3></div></div><div></div></div><p>
	The following are standard event names:
      </p><div class="variablelist"><dl><dt><span class="term"><tt class="literal">Shell::Ready</tt></span></dt><dd><p>
	      The shell has finished loading the plugins and it's
	      ready to begin operations
	    </p></dd><dt><span class="term"><tt class="literal">Shell::Quit</tt></span></dt><dd><p>
	      The user wants to exit the application
	    </p></dd><dt><span class="term"><tt class="literal">Session::Loaded</tt></span></dt><dd><p>
	      The session plugin has finished loading the session
	      data, which is now available at
	      <tt class="literal">/Session</tt>
	    </p></dd><dt><span class="term"><tt class="literal">Session::Save</tt></span></dt><dd><p>
	      The session plugin requests that all interested parties
	      save session data to <tt class="literal">/Session</tt>
	    </p></dd></dl></div></div></div><div class="sect1" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="startup-shutdown"></a>Startup and Shutdown sequence</h2></div></div><div></div></div><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
	The session management is implemented by a plugin which also
	has the capability of opening session files (i.e. it registers
	an open handler).  The session data itself is sustained and
	accessed by a <span class="type">ValueContainer</span> interface installed
	at <tt class="literal">/Session</tt>.
      </p></div><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
	The plugin which installs the default command handler will be
	responsible for opening the files provided in the command
	line. Most likely this will be the document manager plugin
	(which should implement the <span class="type">FileOpen</span> service).
      </p></div><p>
      The creation of a new shell involves the following operations
      and events:
    </p><div class="itemizedlist"><ul type="disc"><li><p>the shell is created</p></li><li><p>all the tools in the shell toolset are loaded in
	  order</p></li><li><p>if this is the first shell created, it invokes
	  all registered command handlers using the provided command
	  line parameters</p></li><li><p>the <tt class="literal">Shell::Ready</tt> event it emitted</p><div class="itemizedlist"><ul type="circle"><li><p>if there is a session plugin</p><div class="itemizedlist"><ul type="disc"><li><p>on emission of the
		  <tt class="literal">Shell::Ready</tt> event, it loads the
		  session file (either the default, or one specified
		  in the command line) and emits
		  <tt class="literal">Session::Loaded</tt></p></li><li><p>all interested plugins can now load
		  the session settings by registering to listen to the
		  <tt class="literal">Session::Loaded</tt>
		  event</p></li></ul></div></li></ul></div></li></ul></div><p>
      On shutdown (when the user quits the application or closes the shell
      window), the following sequence takes place:
    </p><div class="itemizedlist"><ul type="disc"><li><p>the event <tt class="literal">Shell::Quit</tt> is
	  emitted</p></li><li><p>all plugins can perform shutdown operations,
	  including asking the user to save unsaved
	  data</p></li><li><p>if there is a session plugin</p><div class="itemizedlist"><ul type="circle"><li><p>emits the <tt class="literal">Session::Save</tt> event</p><div class="itemizedlist"><ul type="disc"><li><p>all registered plugins save session data
		  to <tt class="literal">/Session</tt></p></li></ul></div></li><li><p>save the session file and perform shutdown
	      operations</p></li></ul></div></li><li><p>any plugin might cancel the shutdown by calling
	  a method on the shell interface (FIXME: define this
	  somewhere)</p></li><li><p>all tools are unloaded in reversed order</p></li><li><p>the shell is destroyed</p></li></ul></div><p>
      Loading a new session implies creating a new shell with the
      specified session file. (FIXME: define a shell service method to
      create a new shell).
    </p></div></div></body></html>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.