tag:blogger.com,1999:blog-84020464274161839102024-02-19T08:32:59.525-05:00Regrettable HacksA blog about Orion, JavaScript, and so on.Anonymoushttp://www.blogger.com/profile/14827225773139461922noreply@blogger.comBlogger4125tag:blogger.com,1999:blog-8402046427416183910.post-35204844345049641872013-01-31T18:12:00.000-05:002013-09-27T10:48:46.216-04:00How not to write a subclass<p>As a programmer coming to JavaScript from the Java world (arguably a bad influence) and the ActionScript world (undoubtedly an even worse one), learning how JavaScript works has been a gradual and error-prone process. I've recently been working on a <a href="http://wiki.eclipse.org/Orion/Coding_conventions">Coding conventions</a> page on the <a href="http://wiki.eclipse.org/Orion">Orion wiki</a>, and a big chunk of that page is devoted to avoiding the kind of common JavaScript traps that I'm always falling into.</p>
<p>Today I wrote a section about creating classes, taking care to point out a mistake that I'd made dozens of times in the past. This post is adapted from there, so go <a href="http://wiki.eclipse.org/Orion/Coding_conventions#Classes">read the original writeup</a> if you're not interested in the extra verbiage.
</p>
<p>First, here's how we tend to create classes in Orion's JavaScript code. I won't claim this is the One True Way, just an easy and straightforward convention that we've settled on.<!-- This class models a Duck, with one instance field and one method. -->
</p>
<pre class="orion-code small" data-editor-lang="js">function Duck(name) {
this.name = name;
}
Duck.prototype.greet = function() {
console.log("Quack quack, I'm a duck named " + this.name);
};
</pre>
<h2>What <code><span class="js-keyword">new</span></code> really does</h2>
Let's look at this piece of code, which is familiar to any JavaScript programmer:
<pre class="orion-code small" data-editor-lang="js">new Duck("Robert");
</pre>
<p>It obviously creates a new instance of <code>Duck</code>. But what is the <code><span class="js-keyword">new</span></code> operator actually doing here? Well, it performs an algorithm that we can break into 4 distinct steps:
<ol>
<li>Create a brand-new object (call it <code>O</code>).</li>
<li>Set <code>O</code>'s prototype equal to <code>Duck.prototype</code>.</li>
<li>Invoke the <code>Duck</code> function with <code><span class="js-keyword">this</span></code> equal to <code>O</code>, and the <code>name</code> parameter equal to <code><span class="js-string">"Robert"</span></code>.</li>
<li>Return <code>O</code>.</li>
</ol>
<h2>Subclasses</h2>
A problem arises when we want to extend an existing class with new behavior. How do we create a prototype for the subclass such that it extends the superclass's prototype?
<p>Here's the wrong way:</p>
<pre class="orion-code small" data-editor-lang="js">function SeaDuck(name, diveDepth) {
Duck.call(this, name); // call the super constructor
this.diveDepth = diveDepth;
}
SeaDuck.prototype = new Duck(); // XXX wrong
SeaDuck.prototype.dive = function() {
console.log(this.name + " dived to a depth of " + this.diveDepth);
};
</pre>
<p>Everything here is reasonable except the XXX'd line, <span class="badcode"><code>SeaDuck.prototype = <span class="js-keyword">new</span> Duck()</code></span>. This part is wrong. (Unfortunately, many occurrences of this pattern remain in the Orion source code, lots of them written by me, which still need to be cleaned up.) So what's wrong with it?</p>
<ul>
<li><b>It's inefficient</b>, since <code>SeaDuck.prototype</code> has fields created by <code>Duck</code> that are never used (like <code>name</code>, for example). Any work done in the <code>Duck</code> constructor is useless to us here.</li>
<li><b>It's fragile</b>, since the proper operation of this code relies on <code>Duck</code> not validating its input parameters. If anyone ever changes <code>Duck</code> to assert that it receives a valid name, our <code>SeaDuck</code> code will blow up. (And sure: we could pass in a fake name to satisfy it, but that clutters up our code with even more useless data).
</ul>
<p><!--The root of our mistake is a conceptual error. A prototype object holds the traits shared among Ducks, but is not itself a Duck; it's a Duck-prototype. The SeaDuck prototype should likewise not be a Duck or a SeaDuck, but rather a SeaDuck-prototype. What we really want, then, is
What we really want is for <code>SeaDuck</code>s to get a new prototype object that inherits all the traits of <code>Duck.prototype</code>. That implies we need
-->For our <code>SeaDuck.prototype</code>, what we really want is <em>not</em> to call the Duck constructor, but just to create a new object whose prototype is set to <code>Duck.prototype</code>. In other words, we only want steps #1, #2, and #4 of the <code><span class="js-keyword">new</span></code> algorithm, not #3.
<h2>Object.create</h2>
For a long time JavaScript provided no way to decouple object creation and prototype-setting from initialization. ECMAScript 5 finally fixed this problem by introducing <a href="https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create"><code>Object.create</code></a>. Among other things, <code>Object.create</code> allows you to build a new object and tell the JS engine exactly what its prototype should be. Just pass the desired prototype object as the first argument. Easy!
<pre class="orion-code small" data-editor-lang="js">SeaDuck.protoype = Object.create(Duck.prototype);</pre>
<p>And that's exactly what we wanted. In fact, we can use <code>Object.create</code> to replace even legitimate uses of the <code><span class="js-keyword">new</span></code> operator. Instead of this:</p>
<pre class="orion-code small" data-editor-lang="js">var robert = new Duck("Robert");
</pre>
<p>…We can write this, effectively re-implementing the <code><span class="js-keyword">new</span></code> algorithm by hand:</p>
<pre class="orion-code small" data-editor-lang="js">
var robert = Object.create(Duck.prototype);
Duck.call(robert, "Robert");
</pre>
<p>While this might be conceptually clearer, it's wildly verbose, so I'd recommend sticking with <code><span class="js-keyword">new</span></code> in these cases.</p>
<p>But what if you're coding for a crap browser like Internet Explorer 8, which <a href="http://kangax.github.com/es5-compat-table/">doesn't support Object.create</a>? Then you need to write your own utility, typically called "beget":</p>
<pre class="orion-code small" data-editor-lang="js">
function beget(obj) {
function BogusConstructor() {}
BogusConstructor.prototype = obj;
return new BogusConstructor();
}
SeaDuck.prototype = beget(Duck.prototype);
</pre>
<p><code>beget</code> avoids the problem I pointed out in the previous section by creating a new <code>BogusConstructor</code> every time it's called, which does no initialization work and only exists to achieve point #3 of the <code><span class="js-keyword">new</span></code> algorithm.
</p>
<p>You could also turn <code>beget</code> into a partial shim for Object.create. I say partial because Object.create also deals with property descriptors, which are impossible to shim.</p>
<h2>The <code>prototype</code>, "prototype", [[Prototype]], and <code>__proto__</code> mess</h2>
Looking back, a big source of my confusion as a learner was in understanding how the <code>prototype</code> property of functions relates to an "object's prototype" and how that in turn affects property lookup. You can easily see that regular objects don't have a <code>prototype</code> property: try evaluating <span class="codeblock"><code>({ }).prototype</code></span> in your debugger. So what's this "prototype" thing everyone keeps talking about on objects? Well, here's my attempt to clarify things, in point form:
<ol style="list-style-type: lower-roman;">
<li>When we say "an object's prototype", we're referring to an internal property of the object, which the ECMAScript spec calls [[Prototype]]. Being an internal property, [[Property]] is not observable from regular ECMAScript code.</li>
<li>An object's [[Prototype]] is consulted to resolve property names when the dot <code>.</code> and array index <code>[]</code> operators are used on the object. If the desired property name is not found in [[Prototype]], then the [[Prototype]]'s [[Prototype]] is consulted, and so on. When people talk about the <em>prototype chain</em>, this is what they mean.</li>
<li>The <code>prototype</code> property can be set on a function. When some function <code>F</code> is invoked as a constructor through the <code><span class="js-keyword">new</span></code> operator, the value of <code>F.prototype</code> becomes the [[Prototype]] property of the newly-constructed object.</li>
<li>To create a new object whose [[Prototype]] is some existing object <code>P</code>, use <code>Object.create(P)</code>.</li>
<li>I lied a bit in point (i). Most JavaScript engines provide a non-standard alias for the internal [[Prototype]] property. It's called <b><code>__proto__</code></b>. (ES5 has defined a standardized <code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf">Object.getPrototypeOf</a></code>, so use that instead of __proto__!)
</li>
</ol>
<p>While you can't rely on <code>__proto__</code> in production code, it's great for debugging, and for fixing your mental model of how the JS engine works. Here's what a <code>SeaDuck</code>'s <code>__proto__</code> looks like in the JS console:
</p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmCklsStj26loBE8tuBHu65h4Ngyup0VKM1VqrN26Yw7asdRuMUGD77hmEfV0DTPoIi3ZsXsGvWLbz0t4UwFVbGrTExAjIPI5hlgc8e_H4h9idZ6H10goGqES1Rr6t0FdgevGmxEG2m9g/s1600/SeaDuck_proto_annotated.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmCklsStj26loBE8tuBHu65h4Ngyup0VKM1VqrN26Yw7asdRuMUGD77hmEfV0DTPoIi3ZsXsGvWLbz0t4UwFVbGrTExAjIPI5hlgc8e_H4h9idZ6H10goGqES1Rr6t0FdgevGmxEG2m9g/s1600/SeaDuck_proto_annotated.png" /></a>
</div>
<p>Note how you can expand the <code>__proto__</code> chain all the way up to <code>Object.prototype</code>.</p>Anonymoushttp://www.blogger.com/profile/14827225773139461922noreply@blogger.com0Ottawa, ON, Canada45.4215296 -75.69719309999999344.7060676 -76.988086599999988 46.1369916 -74.4062996tag:blogger.com,1999:blog-8402046427416183910.post-27125093640501632132012-10-24T17:50:00.000-04:002012-10-24T22:55:44.180-04:00Extending Orion's Settings page with plugin settings<p>Things are pretty wild in the Orion world right now. We're coming up fast on our 1.0 release (<em>**balloons fall from ceiling**</em>). So given how busy we all are, this seems like a great time to stop fixing important last-minute bugs and instead write a leisurely blog post.</p>
<p>Here I'll explain a new feature that landed in <a href="http://planetorion.org/news/2012/08/orion-1-0-m1-new-and-noteworthy/">Orion 1.0M1</a>: <dfn><b>plugin settings</b></dfn>. Plugin settings provide a way for your Orion <a href="http://wiki.eclipse.org/Orion/How_Tos/Writing_a_plugin">plugin</a> to contribute settings to Orion's <a href="http://wiki.eclipse.org/Orion/Documentation/User_Guide/Reference/Settings_page">Settings page</a>. You tell Orion about your setting, and it generates a user interface for manipulating your setting's value. That user interface lives on Orion's Settings page. To receive updates about the value of a setting, your plugin registers a service that will be invoked by the Orion platform to deliver notifications.
</p>
<p>To show how this looks from a user's point of view, check out this predefined setting that ships with Orion 1.0:</p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQVx264lW4jG522L3CNR-r_CF1CSA0J88a8RMTolSbX-zFWiL7oOaTIJ7fdNBq9JVN9VonQP5Yjc3gyEk359gvXwwYOusalI7oLKQ_jtL7IMO6aTLHfx4D3PAYEy7iRy7R5kIPeEYMUB8/s1600/orion-jslint-plugin-setting.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="244" width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQVx264lW4jG522L3CNR-r_CF1CSA0J88a8RMTolSbX-zFWiL7oOaTIJ7fdNBq9JVN9VonQP5Yjc3gyEk359gvXwwYOusalI7oLKQ_jtL7IMO6aTLHfx4D3PAYEy7iRy7R5kIPeEYMUB8/s600/orion-jslint-plugin-setting.png" aria-labelledby="jslintcaption" /></a></div>
<div class="caption" id="jslintcaption">JSLint validation (click for larger).</div>
</p>
<p>This setting lets you customize the options passed to the JSLint validator that checks your JavaScript files for problems. You can turn off annoying validation behavior globally by putting your preferred flags in here — for example, filling in <code>eqeqeq:false</code> will tell JSLint to tolerate JavaScript's type-coercing <code>==</code> and <code>!=</code> operators without complaining<a href="#eqeqnote">*</a>.</p>
<p>Naturally, this setting is contributed by Orion's JSLint Plugin itself, which ships bundled with Orion. This is where things get a little more interesting: if you went ahead and hacked your Orion environment to remove the JSLint Plugin, this setting would disappear from the UI as well. Makes sense, right? This also emphasizes a fact that I think will become increasingly important as Orion matures: <b>plugins are not one-trick ponies</b> (or one-service ponies, to be exact). Rather, they are ponies that can include a whole package of related functionality supporting a given task or feature set.<!-- To summarize: plugins are ponies. That's what you should take away from this.--></p>
<div class="separator" style="clear: both; text-align: center;">
<img border="0" height="240" width="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyGhy7shywV_kAQzPwmbSyWUSAYLUVI_eWJ95NTGIRdi7OwqoHVrqOyM95BM2l9PJiGs6n2FfOpYJJvihKsKSNUI2l2J2nKNNMZ1QxEjPNAzW9WmtSonB_HzLFBysoZIHy64b-FMUwoxo/s240/pony.jpg" /></div>
<div class="caption">A plugin.</div>
</p>
<!-- Much like an overloaded pack horse struggling to keep its footing. -->
<!-- TODO: move this code into the examples section or something? too much here? -->
<p>OK, back to settings. Here's the second setting I mentioned: the New Tab/Same Tab option.</p>
<p><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKO2pcSaBn3k5PnTBOHZqDtKRn-9JeDe86UjqrqPWJFQkTM18UJWaV8s7QZDXmLxeok8nKicrfYasje7y8KVlBJgrHrCgRY4cXHKQq4QBbn8RKwMrFbUQZObfdcE93OiztJvH-EvhBmhc/s1600/orion-newtab-plugin-setting-open.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="241" width="600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKO2pcSaBn3k5PnTBOHZqDtKRn-9JeDe86UjqrqPWJFQkTM18UJWaV8s7QZDXmLxeok8nKicrfYasje7y8KVlBJgrHrCgRY4cXHKQq4QBbn8RKwMrFbUQZObfdcE93OiztJvH-EvhBmhc/s600/orion-newtab-plugin-setting-open.png" alt="New Tab/Same Tab setting" aria-labelledby="newtabcaption" /></a></div>
<div class="caption" id="newtabcaption">New Tab/Same Tab (click for larger).</div>
</p>
<p>This thing controls whether the links in the Navigator (and a few other places) open in a new browser tab. You may recall a similar option from Orion 0.5, but back then it was implemented in a rather ad hoc way, and now it's a plugin setting, which is much cooler. Note how this setting gets a drop-down menu: this is how our generated UI presents a setting whose value is restricted to an enumeration of discrete options.
</p>
<h2>Show me the code, already</h2>
<p><em>Note: I've omitted a few lines of boilerplate plugin code in these examples. If you're unfamiliar with writing Orion plugins, check out the <a href="http://wiki.eclipse.org/Orion/How_Tos/Writing_a_plugin">Simple Plugin Example</a> from the Orion Developer Guide.
</em></p>
<p>So how do you, the plugin author — (if you're not a plugin author, pretend you are) — how do you write one of these plugin settings? Like much of Orion, the heavy lifting happens declaratively through <a href="http://wiki.eclipse.org/Orion/Documentation/Developer_Guide/Architecture#Service_definitions">service properties</a>. To contribute a setting, we register a service with some properties telling the Orion Settings page about it. Here's what the code for that service registration looks like:</p>
<div>
<div class="codeblock small"><code>pluginProvider.registerService(<span class="js-string">"orion.core.setting"</span>, {}, {
settings: [{
pid: <span class="js-string">"nav.config"</span>,
name: <span class="js-string">"Navigation"</span>,
category: <span class="js-string">"general"</span>,
properties: [ {
id: <span class="js-string">"links.newtab"</span>,
name: <span class="js-string">"Links"</span>,
type: <span class="js-string">"boolean"</span>,
defaultValue: <span class="js-keyword">false</span>,
options: [
{value: <span class="js-keyword">true</span>, label: <span class="js-string">"Open in new tab"</span>},
{value: <span class="js-keyword">false</span>, label: <span class="js-string">"Open in same tab"</span>}
]
}]
}]
});</code></div>
<div class="caption">Service registration for the "New Tab" setting.</div>
</div>
<p>You should be able to infer the meaning of most of the fields here by comparing the code to the picture above. But do note that:</p>
<ul>
<li>A single service registration can contribute many settings. (Here, ours contributes just one, <code>'nav.config'</code>).
<li>A single setting can contain several properties. (Here, ours has just one, <code>'links.newtab'</code>).</li>
<li>The identifier for a setting is known as a <dfn>PID</dfn>.
</ul>
<p>For more details about this service API, see <a href="http://wiki.eclipse.org/Orion/Documentation/Developer_Guide/Plugging_into_the_settings_page">its entry in the Orion developer guide</a>.
</p>
<h2>Staying up to date</h2>
<p>Shoving your setting into the UI is all well and good, but it's not very useful if nobody cares when its value gets changed. For a setting to be useful, it has to affect the world somehow. That's where the <code>orion.cm.managedservice</code> <a href="http://wiki.eclipse.org/Orion/Architecture#Extensions">extension point</a> comes in. Your plugin implements one of these <defn>Managed Services</dfn> in order to receive updates from Orion about the setting.</p>
<p>The API for receiving an update is very simple. It looks like this:</p>
<div>
<div class="codeblock small"><code>
updated: <span class="js-keyword">function</span>(properties)
</code></div>
<div class="caption" id="managedServiceCaption">Interface of <code>orion.cm.managedservice</code>.</div>
</div>
<p>That's it: just one method with the signature <code>updated(properties)</code>. The framework calls this method to notify your service of a setting change, passing the values of all your setting's properties in the <code>properties</code> object. Like all service calls in Orion, this happens asynchronously. In addition to that method, your service registration must have a property named <code>'pid'</code>, giving the PID (that is, the identifier) of the setting that it's interested in. Here's what a minimal ManagedService implementation looks like:
</p>
<div>
<div class="codeblock small"><code>pluginProvider.registerService(<span class="js-string">"orion.cm.managedservice"</span>,
{
updated: <span class="js-keyword">function</span>(properties) {
<span class="js-comment">// The "nav.config" setting was updated</span>
}
}, {
pid: <span class="js-string">"nav.config"</span>
});</code></div>
<div class="caption">Minimal ManagedService for the "nav.config" PID.</div>
</div>
<p>However, the real power of this stuff comes in when your Managed Service plays several roles. In Orion's service framework, a single service can be registered under several <em>service names</em> — or in other words, a single service may implement several extension points. Think of orion.cm.managedservice, then, as an optional interface that can be implemented by an existing service to make it configurable by the framework.</p>
<p>For example, in the case of the "JSLint validation options" setting I mentioned earlier, we initially started out with just a validation service — ie. a service with the name "<a href="http://wiki.eclipse.org/Orion/Documentation/Developer_Guide/Plugging_into_the_editor#orion.edit.validator">orion.edit.validator</a>". When I wanted to make it configurable via the setting, I added the service name "orion.cm.managedservice", along with the <code>pid</code> property and the <code>updated()</code> method to fulfill the ManagedService contract. The final result ends up looking like this:</p>
<div>
<div class="codeblock small"><code><span class="js-keyword">var</span> myOptions;
pluginProvider.registerService([<span class="js-string">"orion.cm.managedservice"</span>, <span class="js-string">"orion.edit.validator"</span>],
{
updated: <span class="js-keyword">function</span>(properties) {
myOptions = properties.validationOptions;
},
checkSyntax: <span class="js-keyword">function</span>(title, contents) {
<span class="js-comment">// Validate using our options</span>
<span class="js-keyword">return</span> JSLINT(contents, myOptions);
}
}, {
pid: <span class="js-string">"jslint.config"</span>,
contentType: [<span class="js-string">"application/javascript"</span>] <span class="js-comment">// This is for orion.edit.validator</span>
});</code>
</div>
<div class="caption">A configurable JSLint validator (simplified for clarity).</div>
</div>
<p>As an added bonus, the framework guarantees that, when your service is "managed" (ie. when it implements orion.cm.managedservice), its <code>updated()</code> method will be called to deliver the setting's value <em>before any of the service's other methods are called</em>. So in the code above, our validator knows that <code>myOptions</code> will be set before <code>checkSyntax</code> is invoked to validate a file. This is particularly nice because most Orion plugins are activated <dfn>lazily</dfn> (that is, only when one of their services needs to be called), and plugin authors would otherwise have deal with the possibility that one of their service methods might be invoked before the service had received its configuration. As is, however, the framework saves you from having to worry about that.
</p>
<h2>Under the hood</h2>
<p><em>(This section talks about internal details, which aren't necessary to use the Settings API. <a href="#externallinks">Skip it</a> if you're not interested.)</em>
</p>
<p>If you've worked with <a href="http://en.wikipedia.org/wiki/OSGi">OSGi</a> frameworks before, you may be familiar with the OSGi concepts of <em>managed services</em> and <em>metatypes</em>. Orion has both Managed Services and Metatypes, which are similar to OSGi's (albeit greatly simplified), and they form the low-level building blocks upon which the Orion Settings API is implemented.</p>
<p>Orion manages Managed Services using a service called ConfigAdmin (another borrowed OSGi-ism). The ConfigAdmin is provided by the Orion platform (<dfn>platform</dfn> being a convenient word to describe the low-level plugin and service plumbing). The ConfigAdmin starts up very early in the page's lifecycle, and from then on, monitors the registration of Managed Services and calls their <code>updated()</code> methods when necessary.</p>
<p>A <dfn>Metatype</dfn> describes the shape of an object: that is, what properties it can have, their data types, and default values. If this sounds similar to what goes into a setting, it is: in fact the set of properties within a single setting actually <em>comprise</em> a Metatype. Every setting defined through the orion.core.setting API is internally translated into a Metatype definition that describes the setting's properties. A setting, then, is basically just a thin layer on top of a Metatype, combining the Metatype with a PID, and some additional information telling the Settings page how to categorize the setting (the <code>category</code> field).
</p>
<p>Far from being just an identifier for settings, a PID provides the crucial linkage between a Managed Service and Metatypes. A Metatype can be "designated" (associated) to a PID. This association informs the framework of the data shape (properties) that a Managed Service having that same PID expects to be configured with.
</p>
<p>In summary, Managed Services are the services that expect to be configured with properties, Metatypes describe what properties they receive, the PID is what connects them, and Settings provide a convenient intersection between all three concepts.</p>
<p>For more details on these APIs, see the links below.</p>
<h2><a id="externallinks">More reading</a></h2>
<ul>
<li><a href="http://wiki.eclipse.org/Orion/Documentation/Developer_Guide/Plugging_into_the_settings_page">Plugging into the Settings page</a> from the Orion Developer guide — Explains the <code>orion.core.setting</code> service in detail.</li>
<li><a href="http://wiki.eclipse.org/Orion/Documentation/Developer_Guide/Configuration_services">Configuration services</a> from the Orion Developer guide — details on the nuts and bolts of the Managed Service, Metatype, and ConfigAdmin APIs, with code examples.</li>
<li><a href="http://orionhub.org/jsdoc/symbols/orion.settings.Setting.html">orion.settings.Setting JSDoc</a> — Explains how extension data from 'orion.core.settings' is represented as JavaScript objects.</li>
<li><a href="http://orionhub.org/jsdoc/symbols/orion.metatype.MetaTypeRegistry.html">MetaTypeRegisty JSDoc</a> — Provides an API for querying Metatype information from the service registry.</li>
</ul>
<p id="eqeqnote">* The <code>eqeqeq:false</code> option has been renamed to <code>eqeq:true</code> in more recent versions of JSLint.
</p>
<!--
IRRELEVANT CRAP-- WHO CARES
<h2>What not to do</h2>
<p>For a long time we've been concerned with the security impact of allowing plugin authors to directly contribute UI, which is why we've avoided it so far in Orion, only allowing - small amount of control over the UI</p>
<p>Besides that, there were usability considerations
-in particular, I recall one early conversation where Orion developers expressed frustration with the approach to preferences taken by the Eclipse desktop IDE, in which plugins can contribute an arbitrary UI implementation of a preference pane.</p>
<p>One source of inspiration was Firefox's secret <code><a href="http://kb.mozillazine.org/About:config">about:config</a></code> menu,
- but we wanted something more self-documenting.
- conclusion</p>
-->
<!-- THIS IS TOO DETAILED
<dl>
<dt>data type (string)</dt>
<dd><em>(Required)</em> What value type does this setting have? Can be <code>"boolean"</code>, <code>"string"</code>, or <code>"number"</code>.</dd>
<dt>options (Array)</dt>
<dd><em>(Optional)</em> If <b>options</b> is used, it gives the set of possible values that the setting can take. This will cause the setting's UI to be presented as a drop-down menu. Each option is a literal value must be of the same type as the setting's <b>data type</b>.</dd>
<dt>optionLabels (Array)</dt>
<dd><em>(Required if <b>options</b> is used)</em> Give the user-facing label to associate with each of the possible option values. This text is what appears in the choices of the drop-down menu.</dd>
<dt>defaultValue (number|string|boolean)</dt>
<dd><em>(Optional)</em> Gives the setting's default value as a literal. If <b>options</b> is used, the default value must be one of the option values. The default value determines the initial state of the setting's UI presentation: for example, the initial checked/unchecked state of a checkbox, or which drop-down menu item is initially selected in a drop-down menu.</dd>
</dl>
-->Anonymoushttp://www.blogger.com/profile/14827225773139461922noreply@blogger.com0Ottawa, ON, Canada45.4215296 -75.697193145.0649016 -76.328907100000009 45.7781576 -75.0654791tag:blogger.com,1999:blog-8402046427416183910.post-83629843547280838772012-04-05T18:06:00.001-04:002012-04-05T18:54:20.991-04:00Connecting Amazon S3 to Orion<p><a href="http://en.wikipedia.org/wiki/Amazon_S3">Amazon S3</a> is a popular cloud file hosting service. For months people have been complaining that there was no way to store your <a href="http://wiki.eclipse.org/Orion">Orion</a> work — that is to say, your files and folders — in S3 so you'd have a fast, reliable, place to store your data in the cloud. (Well OK, these complaints were all from one particular Orion committer, but whatever.)<p>
<p>In any case, I finally opted to write a plugin for Orion that lets you use S3 as a file store. This post talks about my experiences. If you'd rather skip right to the code, go here:</p>
<div class="important-box"><p><a href="https://github.com/mamacdon/orion-s3">github.com/mamacdon/orion-s3</a></p></div>
<h2>The S3 Worldview</h2>
<p>In the S3 storage model, an <dfn>object</dfn> is the basic unit of storage. Objects are resources that you can refer to and manipulate using a <abbr title="Representational state transfer">REST</abbr> API. Objects live in <dfn>buckets</dfn>, which are flat containers that can hold many objects (sometimes millions). As an S3 user, you'll generally create one or more buckets to hold your data. S3 also supports powerful security policies for granting bucket access to other users, but that doesn't concern us here. <!--Recently S3 added the ability to configure --> <!-- security policy, logging, notification, and lifecycle management (none of which are relevant to this article). -->
</p>
<p><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSWieAuJ2WdopaKDXLblU5sGQwhXeuPqKpJWDrgFdleqpUHtsQG8L6OwNOWC4U6V4YNTXHvDe46SWeB7Vj7BovCUn4UkMtVa7rrN5jR9_eSiCLUja1IrRfqMHetBKvMpFIDfEVoLMl7dQ/s1600/s3-concepts.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="227" width="323" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSWieAuJ2WdopaKDXLblU5sGQwhXeuPqKpJWDrgFdleqpUHtsQG8L6OwNOWC4U6V4YNTXHvDe46SWeB7Vj7BovCUn4UkMtVa7rrN5jR9_eSiCLUja1IrRfqMHetBKvMpFIDfEVoLMl7dQ/s400/s3-concepts.png" title="Some S3 concepts (click for larger)" /></a></div>
<div class="caption">Some S3 concepts.</div>
</p>
<h2>The Orion File Model</h2>
<p>Orion's file support is based on the concept of filesystems. The model here is decidedly more traditional than S3's:</p>
<p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglLldSkQm6HbN9_mTo8Ye5lfKCa-QSqXjZxpqaGurQF9X84roDPqP-o96zxEWNn5oL9F4DRfbf_5vHmtVtzCq66hcG5UamArYO_boKr_TmF_owjMuA7P39alF3Y0NKJBJ7eEIbyGoec7k/s1600/orion-file-concepts.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="311" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglLldSkQm6HbN9_mTo8Ye5lfKCa-QSqXjZxpqaGurQF9X84roDPqP-o96zxEWNn5oL9F4DRfbf_5vHmtVtzCq66hcG5UamArYO_boKr_TmF_owjMuA7P39alF3Y0NKJBJ7eEIbyGoec7k/s400/orion-file-concepts.png" title="Orion file concepts" /></a></div>
<div class="caption">Orion file concepts.</div>
</p>
<p>As the diagram tries to show, a <dfn>filesystem</dfn> is a place accessible to Orion where files can live. At the top level of a filesystem is the <dfn>workspace</dfn>. Within a workspace are <dfn>folders</dfn> and <dfn>files</dfn>. Naturally, folders can contain files and other folders, nested to an arbitrary depth.
</p>
<p><em>NOTE: The file API is still evolving: see the <a href="http://wiki.eclipse.org/Orion/Documentation/Developer_Guide/Core_client_services#orion.core.file">Client API documentation</a> for details.</em></p>
<h2>From S3 to Orion</h2>
<p>The important thing to note about S3 is that the bucket is the only unit of containment. Every object is contained by one and only one bucket. There is no containment relationship between objects. Initially this might seem like it limits us to a filesystem that is simply a huge, flat list of files. But that's not the case: a hierarchical view can be imposed on a properly-structured bucket. I won't go into detail about how this is done (read the <a href="http://docs.amazonwebservices.com/AmazonS3/latest/dev/ListingKeysHierarchy.html">AWS documentation</a> if you're interested). The upshot is that we can indeed implement the Orion filesystem concepts on top of S3 — although some operations will necessarily be more complex (and likely slower) than if we were connecting to a true hierarchical back end.
</p>
<!--
<p>In S3 this is done through grouping and filtering. Every object in a bucket is uniquely identified by a user-supplied string <dfn>key</dfn>. Objects can be searched and grouped by passing a prefix to match their keys against. By reserving a special character (say, <code>/</code>) as a separator, we construct keys that look much like file paths in a hierarchical system:
</p>
<ul>
<li><code>/file1.txt</code></li>
<li><code>/folder1/file2.txt</code></li>
<li><code>/folder1/folder2/file3.txt</code></li>
</ul>
<p>If our S3 bucket contains the 3 objects shown above, listing the "contents" of folder1 is equivalent to querying all objects whose keys start with the prefix <code>/folder1/</code>, and grouping them using the delimiter <code>/</code>. Creating a new file "inside" folder2 is just writing an object with the key <code class="nobr">/folder1/folder2/someNewFile.txt</code>. Some tasks, like deleting or renaming a folder, are slower and more complex than in a true hierarchical system, but they're still possible. This same approach is used by popular bucket visualization tools like Amazon's own <a href="https://console.aws.amazon.com/s3/home">AWS Console</a>.
</p>
-->
<h2>Extending Orion's File Support</h2>
<p>To connect S3 as an Orion filesystem, we need to write a <a href="http://wiki.eclipse.org/Orion/Architecture#Plugins">plugin</a> that contributes an implementation of a service named <code class="nobr"><a href="http://wiki.eclipse.org/Orion/Documentation/Developer_Guide/Core_client_services#orion.core.file">orion.core.file</a></code>. Various parts of the Orion client UI delegate to implementors of this service. For example, the Orion navigator relies on the <code>fetchChildren()</code> operation, so implementing this function immediately allows the Orion navigator to descend into your filesystem and display child items as nodes in the navigation tree. Implementing <code>read()</code> and <code>write()</code> allows the Orion editor to open and save files that live in your file system. There's also file and folder creation, move, copy, rename, delete, etc. The more of these features you implement, the smoother the integration into Orion will be.
</p>
<div>
<div class="codeblock small"><code>fetchChildren: <span class="js-keyword">function</span>(location) <!--
createWorkspace: <span class="js-keyword">function</span>(name)
loadWorkspaces: <span class="js-keyword">function</span>() -->
loadWorkspace: <span class="js-keyword">function</span>(location) <!--
createProject: <span class="js-keyword">function</span>(url, projectName, serverPath, create) -->
createFolder: <span class="js-keyword">function</span>(parentLocation, folderName)
createFile: <span class="js-keyword">function</span>(parentLocation, fileName)
deleteFile: <span class="js-keyword">function</span>(location)
moveFile: <span class="js-keyword">function</span>(sourceLocation, targetLocation, name)
copyFile: <span class="js-keyword">function</span>(sourceLocation, targetLocation)
read: <span class="js-keyword">function</span>(location, isMetadata)
write: <span class="js-keyword">function</span>(location, contents, args)
remoteImport: <span class="js-keyword">function</span>(targetLocation, options)
remoteExport: <span class="js-keyword">function</span>(sourceLocation, options)
search: <span class="js-keyword">function</span>(location, query)
</code></div>
<div class="caption">List of <a href="http://wiki.eclipse.org/Orion/Documentation/Developer_Guide/Core_client_services#orion.core.file"><code>orion.core.file</code></a> API methods.</div>
</div>
<h2>Domain Topology</h2>
<p>For maximum portability, we want our implementation of the FileClient API to live purely on the client side: no server-side proxies or other hacks allowed. This means all our logic for talking to S3 will run as client-side JavaScript in a browser. Our code will make Ajax requests using the S3 REST API to manipulate S3 objects.
</p>
<p>This imposes one important limitation: the web browser's <a href="http://en.wikipedia.org/wiki/Same_origin_policy">same origin policy</a>. Essentially the plugin (web page) hosting our S3 file service must have the same <dfn>origin</dfn> (protocol + host + port) as the target of all its Ajax requests. So does S3 let us configure a bucket to satisfy this limitation? The answer appears to be no. You can't have, say, a particular path prefix in your bucket configured to host a static website, while other prefixes respond to S3 API calls. A bucket can be configured either to host a static website, or as an S3 endpoint, but not both.
</p>
<p>OK. Can we instead use two separate buckets to overcome this limitation? Yes! Conveniently, S3 allows you to access objects using 3 different URL styles:</p>
<ol>
<li><code>http://s3.amazonaws.com/<u>bucket</u>/key</code></li>
<li><code>http://<u>bucket</u>.s3.amazonaws.com/key</code></li>
<li><code>http://<u>bucket</u>/key</code><br />(where <u>bucket</u> is a DNS CNAME record pointing to bucket.s3.amazonaws.com)</li>
</ol>
<p>Notice that while URLs #2 and #3 have the bucket name as part of the host name (hence tying the origin to a single bucket), URL #1 does not. Using the style of URL #1, our plugin can be hosted from one bucket (configured as a publicly-readable static website), and still be free to blast Ajax REST requests at a second bucket (configured as a restricted-access S3 endpoint). Both buckets are accessed from the same domain, <code>s3.amazonaws.com</code>, so the browser won't complain about same-origin violations. Below is a diagram showing how this arrangement works out:</p>
<!-- (sometimes called "path-style" syntax in the AWS docs) -->
<p><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgs6AB3Uyl88BaQPV8hA8iJAXWJWpRslmoCKGWgGHCNReGektHSx3UnvCQ029ebQP9C0C4FZ5-1-p3tAsXXthMlwMICO4PbFglsXLJCjbf1Hrpfvsg9qTHYzaOm-VdFW7yK3sk8MseYmbI/s1600/s3-plugin-architecture.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="219" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgs6AB3Uyl88BaQPV8hA8iJAXWJWpRslmoCKGWgGHCNReGektHSx3UnvCQ029ebQP9C0C4FZ5-1-p3tAsXXthMlwMICO4PbFglsXLJCjbf1Hrpfvsg9qTHYzaOm-VdFW7yK3sk8MseYmbI/s400/s3-plugin-architecture.png" title="Plugin architecture (click for larger image)" /></a>
<div class="caption">High-level view of the architecture.</div>
</div>
</p>
<p>In the diagram above, <b>Bucket 1</b> is publicly-readable. Thus the Orion plugin registry can access and load the plugin without worrying about how to authenticate itself. By contrast, we've restricted access to <b>Bucket 2</b> to just our S3 user account, thus the Ajax requests that are sent its way must be authenticated. The next section explains further. (Also note: I've drawn the Orion client UI in red since access it usually requires some kind of authentication to access. That's not a requirement, however: you can deploy Orion in all sorts of less-secure ways.)
</p>
<h2>Security</h2>
<p>Keeping all the code on the client is convenient, but presents some security challenges. To modify the contents of Bucket 2, all our REST requests must be properly <dfn><a href="http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html">authenticated</a></dfn>. Authentication requires three pieces of information: the request, our <dfn>Access Key</dfn> (public key), and our <dfn>Secret Access Key</dfn> (private key).
</p>
<p>So the plugin needs to know how to authenticate requests, and to authenticate a request it needs your private key. This exposes a limitation in the existing Orion plugin/service framework, which is that there's no secure way to do this. (More generally, there's no way for a plugin to request access to a user setting from the Orion core… but that's a larger issue.)
</p>
<p>My somewhat-inadvisable temporary solution is this: when the plugin requires your keys, it simply opens a JavaScript prompt asking you to paste them in. It also asks for a "passphrase", which it uses to encrypt the keys. The encrypted keys are kept in <a href="https://developer.mozilla.org/en/DOM/Storage#localStorage"><code>localStorage</code></a>, which saves you the trouble of typing them in every time. The passphrase is not persisted, and gets squirreled away inside a closure variable, which vanishes when you close the browser tab.
</p>
<p>The upshot is this: if you close and reopen your browser, the plugin will find the cached keys in local storage, and prompt only for the passphrase so it can decrypt the keys and carry on.
</p>
<p>Now, obviously asking people to paste secret information into a web page is quite dubious. On top of that, storing sensitive information (albeit encrypted) in a localStorage database for a domain that you don't fully control (here, <code>s3.amazonaws.com</code>) is also a very bad idea. Finally, the usability suffers because the keys are tied to your browser, not your Orion user settings. (Hence, if you use a different web browser, you'll have to paste in your keys all over again). So please regard this as a starting point for future work around security and authentication in Orion, <em>not</em> a realistic long-term solution!
</p>
<h2>The End Result</h2>
<p>If the disclaimer in the previous section hasn't scared you off, you can set up the plugin (see <a href="https://github.com/mamacdon/orion-s3#readme">its GitHub readme</a> for installation instructions). It lets you do basic file operations against your S3 bucket, and performs pretty well.
</p>
<p><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiinFQkE5rOEKswJQ21NajLdCD6j57TKXdvHtFxcwgRscr4BGqxSum3BDQCWLXlDEydkbVDuAmYqKlo2gzfF9ELuIsSBRTYraWleumDODsETatlKfmlO_7QrUTEpQMoCI-MaHoiIsryvH0/s1600/s3-plugin-in-action.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="187" width="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiinFQkE5rOEKswJQ21NajLdCD6j57TKXdvHtFxcwgRscr4BGqxSum3BDQCWLXlDEydkbVDuAmYqKlo2gzfF9ELuIsSBRTYraWleumDODsETatlKfmlO_7QrUTEpQMoCI-MaHoiIsryvH0/s400/s3-plugin-in-action.png" title="The plugin in action (click for larger)" /></a>
<div class="caption">Left: Orion showing our plugin in action. Right: The S3 console showing the same contents.</div>
</div>
</p>
<h2>Future Directions</h2>
<p>Storing any sensitive data in localStorage is not a good approach. The naive solution might be to move your private credentials into the Orion preference store, where access to them could at least be managed somehow (perhaps on a per-plugin basis). But while security is one of Orion's concerns, becoming a secure-storage provider is not. In fact, it's probably better to compartmentalize sensitive information so that the Orion core never actually sees your private credentials.
</p>
<p>So how do we accomplish that? One approach would be for the Orion core to simply establish a channel between an <em>signing requester</em> and a <em>signing provider</em>. The requester in this case would be our S3 plugin, and the provider could be any Orion-connected entity who possesses your S3 credentials and knows how to use them to sign a given request. (We can imagine a provider linked to a secure, trusted third-party service running a cloud deployment of something like <a href="http://keepass.info/">KeePass</a>, or even a private provider that is only accessible from your local network.)</p>
</p>
<p>Once the provider–requester channel is established, these pieces talk only to each other. Most importantly, your private key never has to leave the signing provider. This all sounds a bit hand-wavy right now, but remember Orion lives on the web where all kind of crazy ideas are possible.
</p>
<h2>See Also</h2>
<ul>
<li><a href="http://docs.amazonwebservices.com/AmazonS3/latest/dev/Introduction.html">Introduction to Amazon S3</a> — The S3 documentation.</li>
<li><a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=343263">Orion Bug 343263</a> — Simplify the <code>orion.core.file</code> API.</li>
<li><a href="https://github.com/mamacdon/orion-s3">orion-s3 on GitHub</a> — The source code for the S3 plugin.</li>
<li><a href="https://github.com/eclipse/orion.client/blob/master/bundles/org.eclipse.orion.client.core/web/plugins/webdavFilePlugin.html">WebDAV plugin</a> — Ships with Orion, lets you use a WebDAV server as a filesystem.</li>
<li><a href="http://wiki.eclipse.org/Orion/Documentation/Developer_Guide/Core_client_services#orion.core.file">orion.core.file API</a></li>
</ul>Anonymoushttp://www.blogger.com/profile/14827225773139461922noreply@blogger.com0Ottawa, ON, Canada45.4215296 -75.697193145.0649016 -76.328907100000009 45.7781576 -75.0654791tag:blogger.com,1999:blog-8402046427416183910.post-81917545998757666072011-07-12T17:38:00.026-04:002014-07-08T10:49:27.684-04:00Contributing syntax highlighting in Orion<div style="background: #40ff40; padding: 5px;"><p><b>NOTE:</b> The API described in this post is deprecated. Don't use it. Instead, check out <a href="http://planetorion.org/news/2014/02/orion-5-0-syntax-styling-revisited/">this blog post at PlanetOrion</a>, which explains a new & improved highlighting API.
</p>
<p>I'm keeping this post around for reference only.</p>
</div>
<!-- <h2>Introduction</h2> -->
<p>In this post, I'll explain how to write a plugin for <a href="http://wiki.eclipse.org/Orion">Orion</a> that contributes syntax highlighting for JSON files using a TextMate grammar. It will let us go from this:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEBLocZcxm9repik57fl4a72VuxmkXSld0ZT_bHbSmsgYcuEJWHTiRBCcx7p3-9OzxA7X6eeNK2V3xO97jg5Q6ddzXilvSrWsASk_uroEJgMwjCjv0MH4pbb3wCWXVqV1kMHQ4j5L21fw/s1600/0-json-before.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img class="screenshot" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEBLocZcxm9repik57fl4a72VuxmkXSld0ZT_bHbSmsgYcuEJWHTiRBCcx7p3-9OzxA7X6eeNK2V3xO97jg5Q6ddzXilvSrWsASk_uroEJgMwjCjv0MH4pbb3wCWXVqV1kMHQ4j5L21fw/s1600/0-json-before.png" /></a></div>
<p>…to this:</p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9Bf7KX-CacAtIf_L50eu39SIhHNzCpfkLWdZegYUta9N08HddW3xitmsb-g-KtGJnzwCz5DUeAgE7P3NRgWODciHKpReDZoDNdg8e5CXnuLse72yWXGSmugl2GZp7O3JVBVdPXisHleQ/s1600/1-json-after.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img class="screenshot" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9Bf7KX-CacAtIf_L50eu39SIhHNzCpfkLWdZegYUta9N08HddW3xitmsb-g-KtGJnzwCz5DUeAgE7P3NRgWODciHKpReDZoDNdg8e5CXnuLse72yWXGSmugl2GZp7O3JVBVdPXisHleQ/s1600/1-json-after.png" /></a></div>
<p>Which is much nicer on the eyes/brain.</p>
<h2>What you need</h2>
<p>An <a href="http://eclipse.org/orion/">Orionhub</a> account, or a build of <a href="http://download.eclipse.org/e4/orion/drops/S-0.2RC3-201106270200/index.html">Orion 0.2</a> running on your local server.</p>
<h2>Language Grammars</h2>
<p>Orion implements a subset of the <a href="http://manual.macromates.com/en/language_grammars.html">language grammar format</a> used by TextMate,
a popular text editor for Mac OS X. A <dfn>grammar</dfn> gives a declarative description of the structure of files of a certain type (for example, a
<code>.java</code> or <code>.php</code> file). This "structure" can be limited to simple keyword recognition using regular expressions, but the format
provides enough power to recognize more complex language constructs like classes or function definitions. Sometimes a carefully-written grammar can even detect
common syntax errors, such as a missing comma in a list of items.</p>
<p>The grammar provides rules that tell the underlying <code>TextView</code> what CSS class a portion of text should have. The mapping of CSS classes to rules
(<code>font-color</code>, <code>font-weight</code>, etc.) is ultimately determined by the stylesheet applied to the <code>TextView</code>.
</p>
<h2>Finding a Grammar</h2>
<p>We're going to use a JSON grammar, which its authors have made available under a BSD license. Not every grammar you come across in the wild will allow redistribution, so take note.</p>
<p>Download the JSON grammar file from here:</p>
<p class="important"><a href="http://svn.textmate.org/trunk/Bundles/JSON.tmbundle/Syntaxes/JSON.tmLanguage" rel="nofollow">http://svn.textmate.org/trunk/Bundles/JSON.tmbundle/Syntaxes/JSON.tmLanguage</a></p>
<h2>From PList to JavaScript</h2>
<p>If you open the <code>JSON.tmLanguage</code> file you'll see that it's written in a strange XML dialect. It's actually a <a href="http://en.wikipedia.org/wiki/Property_list">Property List</a>
file, which is a common data storage format used in OS X. TextMate's grammars are written as property lists. This poses a problem for all faithful JavaScript programmers, who avoid
XML like the plague.</p>
<p>But don't whip out your validator just yet. It turns out that property lists convert fairly well to JavaScript object literals, and we've written a tool that will do it automatically:</p>
<p class="important"><a href="http://mamacdon.github.com/0.2/tools/grammar/">Orion grammar converter</a></p>
<aside class="note">The tool only accepts XML property lists. If you come across a property list in an ASCII or binary representation, use OS X's <code>plutil</code> command to convert it to XML first.</aside>
<h2>Running the converter</h2>
<p>Go to the grammar converter in your web browser. Find the folder on your computer where you saved the <code>JSON.tmLanguage</code> and drag it onto the textarea shown in the converter. Immediately, the result should appear below it. Click the <b style="button">Clean up</b> button and the converted JavaScript object will be formatted a little more nicely.</p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrAuIjttl0XxgE0_CGgIv1yFcFfJHwJ36jFaocMQ7Vd1ockccovFdfq1IvEvfzPrUXl92hlsK1l4YP9K-JJxRKsXKjbzPnJgT6M68ul7rCCQzCv0X3Ok_aCx2zg9imQOIvg6oMGuo5NZc/s1600/2-converted.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img class="screenshot" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrAuIjttl0XxgE0_CGgIv1yFcFfJHwJ36jFaocMQ7Vd1ockccovFdfq1IvEvfzPrUXl92hlsK1l4YP9K-JJxRKsXKjbzPnJgT6M68ul7rCCQzCv0X3Ok_aCx2zg9imQOIvg6oMGuo5NZc/s1600/2-converted.png" /></a></div>
<p>Copy and paste the JavaScript object from the text field into a new file somewhere. This is our grammar.</p>
<h2>Packaging the Grammar as a Plugin</h2>
<p>Now we have to write some code that registers our grammar with the Orion editor. Orion allows clients to contribute language grammars using the <code><a href="http://wiki.eclipse.org/Orion/Documentation/Developer_Guide/Plugging_into_the_editor#orion.edit.highlighter">orion.edit.highlighter</a></code> service.
<p>First grab a copy of <code><a href="http://mamacdon.github.com/0.2/plugins/json/plugin.js">plugin.js</a></code>, which provides the Orion plugin API. Put it in a new folder called <code>JsonProject</code> in your Orion workspace. In the same folder, create a new file called <code>jsonPlugin.html</code> with the following content:</p>
<p class="codeblock"><code><!DOCTYPE html>
<html>
<head>
<title>JSON highlighting plugin</title>
<script src="plugin.js"></script>
<script>
var jsonGrammar = <span class="highlight">/* Paste grammar object here -- omitted to save space */</span> ;
window.onload = function() {
var provider = new eclipse.PluginProvider();
provider.registerServiceProvider("orion.edit.highlighter", {},
{ type: "grammar",
fileTypes: ["json"],
grammar: jsonGrammar
});
provider.registerServiceProvider("orion.navigate.openWith", {},
{ name: "Orion web editor",
href: "/edit/edit.html#${Location}",
validationProperties: {Name: "*.(json)"}
});
provider.connect();
};
</script>
</head>
<body></body>
</html>
</code></p>
<p>The first <code>registerServiceProvider</code> call is the crucial part: it contributes our grammar and tells the Orion editor to use it for <code>.json</code> files.</p>
The second <code>registerServiceProvider</code> call associates the Orion editor with <code>.json</code> files so you can click them from the Navigator view to open them in the editor.
This is not strictly necessary to get syntax highlighting, but it's usually what you want when you're adding support for a new file type.
</p>
<h2>Installing and Testing</h2>
<p>To install your plugin, you'll need to host it at a web URL. You can do this by launching the <code>JsonProject</code> folder as a standalone site from within Orion — see
<a href="http://wiki.eclipse.org/Orion/Documentation/User_Guide/Getting_started#Launching_your_project_as_a_website">this page</a> for instructions. Or if you have a personal
web server, you can copy the project contents there instead.</p>
<p>Once you have a URL to <code>jsonPlugin.html</code>, visit the Plugins page in Orion. Type the URL into the box and click <b style="button">Install</b>.</p>
<p>Reload the Navigator page so that the plugin we just installed takes effect. Create a new <code>.json</code> file. Click on it and the editor should open. At this point you should
be able to see it recognizing JSON styling as you type:</p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1gbYYdUQvTfpkJ92N4E-N-UKNAjvHAossYmpqIiMG2yHU5pzjZ6w2eB2R95-Z_kPJPyPBxbWFeNmGQJwsruA99PXkeJgfgZpybOskiHxkH2Q6ohLDR4SijPF6zWaSWtsmvxmSV_NS-Eo/s1600/3-styling.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img class="screenshot" border="0" height="155" width="305" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1gbYYdUQvTfpkJ92N4E-N-UKNAjvHAossYmpqIiMG2yHU5pzjZ6w2eB2R95-Z_kPJPyPBxbWFeNmGQJwsruA99PXkeJgfgZpybOskiHxkH2Q6ohLDR4SijPF6zWaSWtsmvxmSV_NS-Eo/s400/3-styling.png" /></a></div>
<h2>Conclusion</h2>
<p>We've seen how to take an existing <code>.tmLanguage</code> file and use it to provide syntax highlighting in Orion.</p>
<h2>See Also</h2>
<ul>
<li><code><a href="http://mamacdon.github.com/0.2/plugins/json/jsonPlugin.html">jsonPlugin.html</a></code> — A pre-built version of the final result, just in case you want to cheat</li>
<li><a href="http://orionhub.org/jsdoc/symbols/orion.editor.TextMateStyler.html">orion.editor.TextMateStyler</a> — Docs explaining limitations of our current grammar support</li>
<li><a href="http://localhost:8080/examples/editor/embeddededitor.html">A smaller example</a> — Shows how grammar support can be used without the overhead of the Orion plugin/service framework</li>
<li><a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=347493">Bug 347493</a> — Discussion on how to contribute additional stylesheets to the editor (future)</li>
<li><a href="http://manual.macromates.com/en/language_grammars">TextMate Language Grammars reference</a></li>
</ul>Anonymoushttp://www.blogger.com/profile/14827225773139461922noreply@blogger.com0Ottawa, ON, Canada45.411572 -75.69819445.1241485 -76.251853 45.6989955 -75.144535