Create IDE Plugins

How to create plugins and tools with the LiveCode 8.0 IDE

by Ali Lloyd on December 21, 2015 1 comment

The LiveCode 8.0 IDE

Several of the IDE’s palettes have been rewritten in LiveCode 8.0 to support widgets, most notably the Tools Palette and the Property Inspector. Also many stacks have become script-only, to enable better version control in the LiveCode IDE Git repository – this includes the project browser, the menu bar, and parts of the script editor.

Since there was so much code rewriting occurring in IDE stacks any, we took the opportunity to update the way the stack interact with user stacks and each other. As much as possible, the functionality and data provision for these stacks now comes from the central IDE library, revIDELibrary. This removes their interdependency, and allows them to be viewed as plugins or extensions. As such they provide examples of how we recommend structuring such extensions.

They take advantage of some new features and abstractions in the 8.0 IDE, which can hopefully be leveraged by you, the plugin authors and tool makers, regardless of whether you are planning to distribute them or not. By using this system as much as possible, you can help refine the functionality for the benefit of all, all the while ensuring that the tools remain compatible and potentially improving the performance of the IDE while they are in use.

Publish and Subscribe

The IDE has to perform a tricky balancing act: on one hand it must provide all the functionality needed by its stacks and by the user during development; on the other it should be as hands-off as possible during execution so as not to disrupt anything. Ideally running a stack in the IDE should be as similar as possible to running it in a standalone.

As a general solution to this problem, the IDE library act as a broker for messages associated with certain events. A single stack in the frontscripts, revIDEMessageHandler, intercepts all engine messages and notifies the IDE library at the next available time, but passes those messages so they propagate through the message path in the usual way.

IDE stacks and plugins should subscribe to messages via the IDE library using the revIDESubscribe command:

revIDESubscribe pMessage, pOptionalSource

If pOptionalSource is not empty, then only messages coming from that source will be delivered to the subscriber. The subscriber is currently always the long id of the target, i.e. the object whose script execution directly resulted in the call to revIDESubscribe.

For example, a plugin that needs to update itself when a new control is created, instead of directly handling the newControl message, should subscribe to the ideNewControl message at an appropriate time (usually during its preOpenStack handler) and handle the ideNewControl message:


on preOpenStack
	revIDESubscribe "ideNewControl"
end preOpenStack

on ideNewControl pControl
	-- pControl is the long id of the newly created control
end ideNewControl

It is not currently possible to subscribe to messages on behalf of other objects, but if this is a desirable feature then it is something we can consider adding in the future.

Some messages are parameterised, for example idePreferenceChanged, which can be subscribed to on a per-preference basis by subscribing to the message

idePreferenceChanged:<preference>

For example, a script editor plugin should honour the cRevScriptSize preference, which will be sent by the IDE library whenever this
preference is changed. Therefore it can subscribe in its preOpenStack handler and respond to the idePreferenceChanged
message:


on preOpenStack
	revIDESubscribe "idePreferenceChanged:cRevScriptSize"
end preOpenStack

on idePreferenceChanged pPreference
	switch pPreference
	case "cRevScriptSize"
		local tValue
		put revIDEGetPreference(pPreference) into tValue
		-- Update with new value of cRevScriptSize
		...
end idePreferenceChanged

The IDE library does not have a fixed set of messages that it is the broker for. Therefore plugins can use the IDE library, if they wish, to send any messages they require. This would in theory enable further independently authored plugins to interact with each other without having to be concerned with checks about whether they are in memory.

Some of the most commonly used messages are

  • ideNewControl pControlID
  • ideControlDeleted pControlID
  • ideNewCard pCardID
  • ideCardDeleted pCardID
  • ideNewStack pStackID
  • ideStackDeleted pStackID
  • ideOpenStack pStackID
  • idePreferenceChanged pPreference
  • ideSelectedObjectChanged
  • ideToolChanged
  • ideNameChanged pOldName, pNewName, pObjectID

Actions

In order for the IDE library to maintain state and keep all palettes up to date, tools can either use the IDE API wrappers around common functionality, ensure that messages are not locked when they perform tasks, or notify the IDE library as appropriate using the revIDEMessageSend handler.

Some actions that tools may need to perform and the current IDE library implementations are listed below:

  • revIDENewMainstack pWidth, pHeight : Creates a new stack with the given width and height.
  • revIDECloseStack pStackID, pDestroy : Closes the stack with long id pStackID, deleting if pDestroy is true
  • revIDEActionNewCard : Creates a new card on the current stack.
  • revIDEActionDeleteCard : Deletes the current card.
  • revIDEInstallExtension pExtensionFolder, pOptionalType : installs the extension residing in the given folder. The type will ultimately be detected from the extension manifest, but passing a pOptionalType temporarily sets the type for potential display purposes while downloading/installing.
  • revIDEUninstallExtension pExtensionTypeID : Uninstalls the extension with the given type ID.
  • revIDECreateObject pObjectTypeID, pTarget, pLoc : Creates a control of the given type on the same stack as pTarget, at loc pLoc. pObjectTypeID is a type ID as returned by the ideObjectTypes function. This has been implemented to disambiguate different control styles of the same type.

For example:

revIDECreateObject "com.livecode.interface.classic.TabPanel", the long id of me, "100,100"

creates a tab panel on the calling card at 100,100.

There are many more such handlers in the script of stack “revIDELibrary”.

Data Provision

There are a number of sets of data that are useful for IDE stacks and plugins that are designed for common IDE tasks. Here are some of the handlers that you may find useful when creating a plugin:

Object Property Data

The following is a selection of handlers in the IDE API relating to object property data. These are used to provide data for the IDE’s Property Inspector.

  • function revIDEPropertyInfo pPropName : Fetch the data for the given object property. This is returned in the form of an array keyed by the various meta-properties, including “default” (default values), “read_only”, “editor” etc.
  • function idePropertyNames pObjectType : Fetch the list of properties that apply to the given object type. pObjectType is a type ID as returned by the ideObjectTypes function.
  • function revIDEObjectPropertyInfo pObjectType, pPropName : Returns the data for the given object property, taking into account potentially overridden values for the given object type.
  • function revIDEPropertiesInfo pObjectList : Returns an array of property data for the given object list, in the structure required by the property inspector.

The ideObjectTypeFromObject function returns the type ID for the object whose long ID is passed as a parameter. This can be used in conjunction with the above to retrieve the property data for a given object.

Tools Data

The following is a selection of handlers in the IDE API relating to available tools data, which are used to generate the IDE’s Tools Palette.

  • function revIDEClassicControls: Returns an array keyed by classic control type, detailing properties associated with each of the classic control tools, including “type” (the actual control type string), “title” and “order”.
  • Similarly, revIDEGraphics, revIDEGraphicTools, and revIDEPaintTools.

Project Data

The following is a selection of handlers in the IDE API relating to current project data, which are used to provide data for the Project Browser.

  • function revIDEMainStacks: Returns a list of the current mainstacks, taking into account the ‘Show IDE stacks in lists’ preference.
  • Functions revIDECardPropertiesOfStack, revIDEControlPropertiesOfCard, revIDEControlPropertiesOfGroup for child object data.

Extension Data

The following is a selection of handlers in the IDE API relating to extension data (in revIDEExtensionLibrary).

  • function revIDEExtensions pType, pStatus : returns an array of data about all the extensions of the given type. pType can currently be either “widget”, “library” or “module”. pStatus can be any of “downloading”, “installed”, “uninstalled” or “error”
  • function revIDEExtensionProperty pKind, pProperty : returns the value of the given meta-property of the extension of the given kind.

For example:

put revIDEExtensionProperty("com.livecode.widget.clock", "install_path")

Some of the useful meta properties of an extension are the following:

Firstly, some given directly in the widget metadata

  • “version”
  • “author”
  • “title” : The extension display name
  • “type” : The type (either “widget”, “library” or “module”)
  • “requires” : A list of the module dependencies
  • “uservisible” : (widgets) whether the widget should be visible to the user (for example in a tools palette)
  • “install_path” : The path to the folder from which the extension was installed

The following may also be present:

  • “icon” : The path to the extension icon
  • “svgicon” : The svg path data for the extension icon
  • “preferredSize” : The size at which the widget should be created

Documentation Data

The following is a selection of handlers in the IDE API relating to documentation data (in revIDEDocumentationLibrary). These are used to provide data for the documentation pane of the script editor.

  • function ideDocsFetchLCSData pEntryName : Fetch the data for entries with name pEntryName in the LiveCode Script dictionary.
  • function ideDocsFetchLCSDataOfType pEntryName, pType: Fetch the data for the entry with name pEntryName and type pType in the LiveCode Script dictionary.
  • function ideDocsFetchLCSElementOfType pEntryName, pType, pElement: Fetch the data for the docs element pElement with entry name pEntryName and type pType in the LiveCode Script dictionary.
  • function ideDocsFetchLCSEntries: Fetch all the data for entries in the LiveCode Script dictionary.
  • Similarly, functions ideDocsFetchLCBData, ideDocsFetchLCBDataOfType, ideDocsFetchLCBElementOfType, ideDocsFetchLCBEntries.
  • function ideDocsFetchExtensionData pID, pEntryName : Fetch the data for entries with name pEntryName in the API for the extension with id pID.
  • Similarly, functions ideDocsFetchExtensionDataOfType, ideDocsFetchExtensionElementOfType, ideDocsFetchExtensionEntries.

Future Plans

Ultimately we plan to prefix all public IDE API handlers with ide rather than revIDE. The idea will be, once a given handler is considered stable and useful enough, to move it to a new library stack called revIDEAPI, document it and enable the documentation to be launched in the dictionary. In order to do this as soon as possible we need your help to test and feed back on the API and its implementation as it stands!

We are also looking into extending the widget package/installation system to all sorts of IDE extensions, providing full integration for custom controls, plugins, libraries etc., including docs and APIs. Potentially all these extensions can co-exist happily in a LiveCode extension ecosystem, and we will be able to provide instantaneous access to a large variety of different add-ons from within the IDE.

Ali LloydHow to create plugins and tools with the LiveCode 8.0 IDE

Related Posts

Take a look at these posts

1 comment

Join the conversation
  • Stam - February 3, 2022 reply

    Excellent resource – is thee more documentation available? Specifically are there other messages that can be subscribed to, for example something like ideStackResized or some such?

Join the conversation

*