Enso Developer Prototype DocumentationFrom Enso Wiki
[edit] WarningWARNING: This guide describes process for the official "Frozen" Enso with "Enso Developer prototype" installed. It is not guide for the "community" (open-source) Enso. If you want to participate in the Community Enso development, please follow the guide on Main Page
The Enso Developer Prototype (EDP) is a lightweight framework that allows Humanized Enso to be extended through the creation of commands. To create commands, a developer needs to create an Enso Extension that registers its commands with Enso. Once a user invokes such a command, Enso contacts the Extension, which continues to interact with Enso to provide the end-user with the functionality they need. At present, all interaction between Enso and the Extension is done via XML-RPC. However, the API is intended to be generic enough that in the future, more remote procedure call protocols (e.g., COM, JSON-RPC, REST) will be supported. Sample code for an XML-RPC Extension written in Python can be found below. [edit] Reference[edit] Message Text XMLMessage Text XML fragments are used to display transparent messages in Enso. Their format is quite simple. The simplest Message XML fragment simply consists of a single string of text enclosed in a paragraph element, as follows: <p>Hello there!</p> This will result in a primary message that consists of the string "Hello there!". A paragraph element may be immediately followed by a caption element, which is displayed in a slightly smaller font and a different color from the paragraph, like so: <p>Hello there!</p><caption>How are you?</caption> If you deviate from this pattern, e.g. using multiple <p> or <caption> tags in the same message, you can get some strange results. All <p> tags in the beginning of the message will be displayed as normal, one per line. After the first <caption> tag, everything will be indented and displayed with a smaller font. That is, both <p> and <caption> tags will be displayed with the same font-size after the first <caption> tag. <p>Big, white</p><p>Big, white</p><caption>Small, green, indented</caption><p>Small, white, indented</p> [edit] Command ExpressionsCommand names are always specified in the format of a Command Expression. For many commands, this will simply be the unique string of characters that form the command's name. But for commands that can take arguments, a Command Expression can take a more complicated form. Command expressions are either strings that are exactly one command name, or are strings containing curly brackets {} to indicate that they take a parameter, e.g.:
minimize
upper case
open {object}
goto {window name}
font size {number}
There must be at most one curly-bracket parameter, and it must come at the end of the string. Additionally, valid command expressions may contain no uppercase letters. [edit] Extension APIThe following interface must be exposed by the Enso Extension. callCommand(name, postfix)
Executes a command.
This function is called immediately after an Enso user invokes
a command that the Enso Extension has registered.
Arguments:
name: the name of the command to execute. This is identical
to the string that the Enso Extension passed in when it
originally registered the command.
postfix: the postfix the Enso user supplied with the
command. If the end-user supplied no postfix or the command
takes no postfix, then this will be an empty string.
Return value:
The return value of this function is ignored.
[edit] Developer APIThe following interface is exposed by the Enso Developer Prototype. displayMessage(text)
Displays the given message text XML fragment as a transparent
message.
Arguments:
text: the message text XML fragment to be displayed.
Return value:
This function always returns True.
Error codes:
6 : Malformed message text XML
10 : Invalid message text XML
getFileSelection()
Retrieves the user's current file selection.
Return value:
List of unicode strings. If the user has selected one or
more files in the current application, each item in the list
is the full pathname to one of the files in the selection.
If no files are selected, or if there's an error trying to
get the files, returns an empty list.
getUnicodeSelection()
Retrieves the user's current text selection.
Return value:
Unicode string representing the user's current selection. If
the user has no selection, or if the selection is not text,
then the return value is an empty string.
insertUnicodeAtCursor(text, fromCommand)
Inserts unicode text at the current cursor location in the
user's current application. If the user currently has a text
selection, the text is appended to it. If the text cannot be
inserted, a transparent message is displayed and the text is
placed in Enso's put buffer.
Arguments:
text: the unicode text to be inserted.
fromCommand: the name of the command that is inserting the
text. This is entirely for informational purposes and will
be displayed to the user if the text cannot be inserted.
Return value:
This function always returns True.
registerCommand(url, name, description, help, postfixType)
Registers an Enso Extension Command with Enso. Once this
function is called, an end-user may invoke the command via
Enso's Command Quasimode.
If the same Extension registers the same command more than
once, the previous registration of the command is
automatically unregistered. However, if two different
Extensions try to register the same command, the second one
will receive an error response.
Arguments:
url: the URL for the Enso Extension endpoint.
name: the name of the command, in Command Expression format.
description: the one-line descriptive text for the command,
displayed on the top line of the Command Quasimode when the
command name has been entered into the quasimode.
help: the XHTML fragment containing detailed help for the
command, displayed when the end-user executes the 'help
<command name>' command.
postfixType: the postfix type of the command. This can be
one of three string values:
'none' : the command takes no postfixes. An example of
such a command is Enso's built-in 'upper case' command.
'bounded' : the command takes a postfix that is one of a
bounded list of choices. An example of such a command
is Enso Launcher's 'open' command.
'arbitrary' : the command takes a postfix that is any
user-supplied text string. An example of such a command
is Enso's built-in 'google' command.
Return value:
This function always returns True.
Error codes:
3 : Command name already registered
5 : Invalid command name
2 : Bad Extension URL
11 : Malformed XHTML
12 : Invalid postfix type
setCommandValidPostfixes(url, name, postfixes)
Sets the valid postfixes for a command that takes a bounded
list of postfixes. The command must have been previously
registered with registerCommand().
Arguments:
url: the URL for the Enso Extension endpoint.
name: the name of the command.
postfixes: a list of strings, each representing a valid
postfix for the commmand.
Return value:
This function always returns True.
Error codes:
4 : Command not registered
2 : Bad Extension URL
setUnicodeSelection(text, fromCommand)
Replaces the user's current selection with unicode text. If
the user has no current selection, the text is inserted at the
current cursor location in the user's current application. If
the text cannot be inserted, a transparent message is
displayed and the text is placed in Enso's put buffer.
Arguments:
text: the unicode text to be inserted.
fromCommand: the name of the command that is inserting the
text. This is entirely for informational purposes and will
be displayed to the user if the text cannot be inserted.
Return value:
This function always returns True.
unregisterCommand(url, name)
Unregisters a command. The command must have been previously
registered with registerCommand().
Once a command is unregistered, it can no longer be invoked by
an Enso end-user.
Arguments:
url: the URL for the Enso Extension endpoint.
name: the name of the command.
Return value:
This function always returns True.
Error codes:
4 : Command not registered
2 : Bad Extension URL
[edit] LimitationsThe prototype is just that: a prototype. It has some limitations (which will be remedied in the near future), and among them are the following:
means that while the EDP Beta Product is running, malicious code can interact with the EDP XML-RPC endpoint to request information about the user's current selection, etc.
hard-coded URL (see the sample code for details). This means that, at present, the Beta Product can only be run on one instance of Enso on a particular machine at a time; for instance, if fast-user switching is enabled and two users are simultaneously running Enso, EDP cannot be running on both of them at once. [edit] Sample Enso Extension code# ---------------------------------------------------------------------------- # Copyright (c) 2007 Humanized, Inc. All rights reserved. # ---------------------------------------------------------------------------- # # SampleEnsoExtension.py # # Python Version - 2.4 # # ---------------------------------------------------------------------------- """ This sample script sets up an Enso Extension for use with the Enso Developer Prototype Beta Product. """ # ---------------------------------------------------------------------------- # Imports # ---------------------------------------------------------------------------- from SimpleXMLRPCServer import SimpleXMLRPCServer import xmlrpclib import socket import time import threading # ---------------------------------------------------------------------------- # Constants # ---------------------------------------------------------------------------- # The full URL for the XML-RPC endpoint of the Enso Developer # Prototype, which we need to connect to in order to access Enso # services. For the time being, this is hard-coded, but in the future # we may use some sort of name service to look it up. XMLRPC_ENDPOINT_URL = "http://127.0.0.1:11374" # The TCP port that our Enso Extension's XML-RPC endpoint will be # located on. EXTENSION_ENDPOINT_PORT = 8620 # The IP address that our Enso Extension's XML-RPC endpoint be located # on. EXTENSION_ENDPOINT_ADDRESS = "127.0.0.1" # The full URL for our Enso Extension's XML-RPC endpoint. EXTENSION_ENDPOINT_URL = "http://%s:%d" % ( EXTENSION_ENDPOINT_ADDRESS, EXTENSION_ENDPOINT_PORT ) # ---------------------------------------------------------------------------- # Server Thread # ---------------------------------------------------------------------------- class ServerThread(threading.Thread): """ Simple thread that encapsulates the running of the server that hosts our Enso Extension's XML-RPC endpoint. """ def __init__( self, address ): threading.Thread.__init__( self ) self._rpcServer = None self._stop = False self._address = address def run( self ): self._rpcServer = SimpleXMLRPCServer( self._address ) # We want to set the timeout so that we can CTRL-C out of the # server on Windows machines. Windows won't let keyboard # interrupts kick a process out of a socket system call, so we # have to listen for incoming connections in 1-second # "chunks". self._rpcServer.socket.settimeout( 1.0 ) self._rpcServer.register_instance( EnsoExtensionMethods() ) while not self._stop: self._rpcServer.handle_request() def stop( self ): self._stop = True class EnsoExtensionMethods( object ): """ Methods for our Enso Extension's XML-RPC endpoint, which implement the Enso Extension API. Every public method here (i.e., every method that doesn't start with an underscore character) is a required method that must be implemented by all Enso Extensions, which Enso will call into when an Extension's services are required. """ def __init__( self ): self.enso = xmlrpclib.ServerProxy( XMLRPC_ENDPOINT_URL ) def callCommand( self, commandName, postfix ): """ Extension API method that is called whenever an Enso user has executed a command that our Extension implements. This method must complete as soon as possible, because it is currently executed synchronously by Enso. 'commandName' is the full name of the command that is to be run. 'postfix' is the postfix (i.e., argument) that the user has specified for the command. For instance, in the 'order {food}' command, this will be a string representing the type of food the end-user wants to order. If a postfix is not specified or not applicable to a command, this will be an empty string. """ if commandName == "translate to swedish": self.__translateToSwedish() elif commandName == "order {food}": self.__orderFood( postfix ) else: raise AssertionError( "Unknown command name: %s" % \ commandName ) # The Enso Developer Prototype doesn't look at this return # value, but we can't return None because it's not part of the # XML-RPC specification, so we'll return True instead. return True def __translateToSwedish( self ): """ Implementation of the 'translate to swedish' command. """ import urllib # URL of the Swedish translation web service. URL = "http://www.cs.utexas.edu/users/jbc/bork/bork.cgi?" # Call an Enso Developer Prototype API function to get the # user's current unicode selection. text = self.enso.getUnicodeSelection() if len( text ) == 0: # Use the Enso Developer Prototype API to display a # transparent primary message to the end-user. self.enso.displayMessage( "<p>No text selected!</p>" ) else: params = { "input": text, "type": "chef" } params = urllib.urlencode( params ) # Note that we're making a network connection here, which # could take a long time to complete. Ideally, this # should be executed in a separate thread, and only if it # takes longer than about 250 ms--the maximum wait time # before end-users start clicking on things--should the # Enso Extension print a primary message telling the # end-user to "please wait". Furthermore, network errors # should be caught and converted into primary messages # informing the end-user. Actually implementing such # behavior is left as an exercise for the reader. translated = urllib.urlopen( URL, params ).read() translated = translated.decode( "iso-8859-1" ) # Use the Enso Developer Prototype API to replace the # user's current unicode selection. The second parameter # is the name of the command that is originating the # change, for display purposes only. If the unicode # selection can't be changed (e.g., because it is static # text on a web page), then the text will automatically be # inserted into Enso's put buffer for insertion at a later # time by the user (via the 'put' command). self.enso.setUnicodeSelection( translated, "translate to swedish" ) def __orderFood( self, foodName ): """ Implementation of the 'order {food}' command. """ # The 'caption' tag can be used to provide a caption for the # main text, displayed in a slightly smaller font and # different color from the main text. self.enso.displayMessage( u"<p>Ordering %s\u2026</p>" "<caption>One moment please.</caption>" % foodName ) def foodOrderConfirmation(): """ Simple thread that fakes the ordering of food and displays a primary message to the user when finished. This shows that Enso Developer Prototype API calls can be made from anywhere--not just from the context of a command execution. """ time.sleep( 3 ) self.enso.displayMessage( "<p>%s ordered!</p>" "<caption>I didn't really order it, though.</caption>" % \ foodName ) thread = threading.Thread( target = foodOrderConfirmation ) # Just in case we kill the Extension, we don't want to # have to wait until the fake food ordering is complete. :) thread.setDaemon( True ) thread.start() if __name__ == "__main__": # For some reason we need to do this or else we'll get annoying # exceptions along the lines of "the socket operation could not # complete without blocking." socket.setdefaulttimeout( 10.0 ) print "Starting XML-RPC server." serverThread = ServerThread( (EXTENSION_ENDPOINT_ADDRESS, EXTENSION_ENDPOINT_PORT) ) serverThread.start() try: enso = xmlrpclib.ServerProxy( XMLRPC_ENDPOINT_URL ) # Register our commands with the Enso Developer Prototype. enso.registerCommand( EXTENSION_ENDPOINT_URL, # Name of the command "translate to swedish", # One-line description of the command, which will be shown # in the command quasimode. "Translates your current selection to Swedish.", # XHTML fragment containing help text for the command, # which will be shown if the user executes the 'help' # command on this command. "<p>This command translates the current" \ "text selection to Swedish. Try it on this " \ "sentence.</p>", # The postfix type of the command; it can either be # 'none', in which case this is an ordinary command that # takes no special postfix argument, 'bounded', in which # case the end-user can choose a postfix from a list of # possibilities, or 'arbitrary', in which case the postfix can # be any user-supplied text string. "none" ) enso.registerCommand( EXTENSION_ENDPOINT_URL, "order {food}", "order some food.", "<p>Allows you to order food.</p>", "bounded" ) # Set valid postfixes for the 'order {food}' bounded command. # This function can be called at any time, if and when the # list of valid postfixes changes. enso.setCommandValidPostfixes( EXTENSION_ENDPOINT_URL, "order {food}", ["pizza", "lingonberries"] ) try: # Now just block for input while our server thread # listens for incoming connections from Enso. print "press enter to exit." raw_input() finally: # Before we leave, let Enso know that we're not offering # our commands anymore. enso.unregisterCommand( EXTENSION_ENDPOINT_URL, "translate to swedish" ) enso.unregisterCommand( EXTENSION_ENDPOINT_URL, "order {food}" ) finally: print "Shutting down XML-RPC server." serverThread.stop() |

