RFC: Exporting JavaScript API

Since quite some time, Kate Part has build-in scripting support through JavaScript. Our plan is to make this API public, so other applications like Kile, Kate App and KDevelop can use it. However, we are currently unsure how to best implement it, so this is a rfc to get feedback.

The bindings for a Kate Document are for instance located in part/script/katescriptdocument.h (header, implementation). As you can see, there are functions like

Q_INVOKABLE bool insertLine(int line, const QString &s),

which can be invoked in our scripting by a call of ‘document.insertLine(5, “hello world”)’. The API only contains basic functions. But for instance Kile maybe also wants to provide a function called ‘document.insertSection()’ or similar LaTeX related functions. The question now is as follows: How can Kile extend our QObject based prototype with their own QObject based classes?

We do not want to make the class KateScriptDocument public. Instead, we just want to return a QScriptValue containing a QObject based KateScriptDocument. You can think of the problem also as follows:

// in Kile:
QScriptEngine *engine = ...;
KTextEditor::Document *kteDocument = ...;

QObject* kateScriptDocument = kteDocument->scriptDocument();
engine->globalObject().setProperty("document", engine->newQObject(kateScriptDocument));
// at this point, the JavaScript object 'document' contains all KateScriptDocument functions

// next, we want to add the Kile related document functions
KileTextDocument* kileDocument = ...;
QObject* kileScriptDocument = kileDocument->...(); // some function that returns the binginds object

// now: how can we populate the 'document' property with the functions in kileScriptDocument?
engine->globalObject().setProperty("document", ? );

If you have any idea or other solutions how to do it right, please let us know!

4 thoughts on “RFC: Exporting JavaScript API”

  1. There are two things you can do, the way I see it:

    1. Use dynamic binding to add methods and properties to the document. Not sure how feasible this is especially when dealing with C++ methods, but basically, instead of having a KileScriptDocument class and setting document to that, you’d use setProperty() on the KateScriptDocument to add properties, including function objects. Basically this is how you would do it in pure JavaScript.

    2. Use the facade pattern. You create a class KileScriptDocument with a member KateScriptDocument. Then you need to implement every method that you want to expose of KateScriptDocument in KileScriptDocument. Leads to quite some duplication, but has the advantage that you can hide methods that would not be relevant for your situation.

    1. 1. maybe I don’t completely get this, but from C++ the Kile developers will want a KiileScriptDocument, as this way functions can easily be exported into js.

      2. This should work. However, if Kate later adds a method, maybe Kile then has to explicitly expose this method as well by a forward call. I’d rather want it to work out of the box. Still, this is probably the best solution currently. Still, I have the feeling that it’s a bad workaround instead of a clean solution…

      1. (Sorry for a late reply)

        Ok, lets rephrase the problem slightly: What you want to do, essentially, is merge two QObjects without one of the objects inheriting the other. This will always be ugly, the “clean” solution is inheritance since it is the only thing within the language that allows you to reuse methods from a different object.

        Now, that said, merging QObjects like this is not /impossible/, since Qt gives us the introspection abilities that are needed for it. As said, it just becomes ugly. Thinking about it with a bit of a fresh perspective, one possibility that is still ugly but maybe a little easier from the perspective of a Kile developer is to combine 1 and a reverse of 2. So:

        – KateScriptDocument has a list property with objects that “extend” it. (A little nasty on the developer side since setting QList properties through only setProperty is… tedious to put it mildly.)
        – When this list property is changed, it is iterated through and for each object it does the following:
        – Find all methods that need to be exported
        – For each method, create a QtScript function object that internally calls the method of the extension object.
        – Set a property on the KateScriptDocument with the name of the function and the function object as value.

        Now, as I said, this will still be ugly code with a lot of issues and a lot of chance of breakage, but (at least in theory) it should work. Personally I think the original option 2 is cleaner, if harder on the developers.

        1. I also had this solution in mind: In JS you can also list all functions of an object with
          for (prop in someObject) { other.prop = prop; }

          So moving functions could also be done in JS itself. But again, this solution doesn’t look nice.

          Inheritance is the cleanest way, right. But that binds us (unneccessarily?) on the BIC side.

Leave a Reply