Monday, September 15, 2014

Snippets in Kate 5

Recently I spent some time to port and clean up the Snippets plugin and the underlying template interface for Kate 5.  It's now fully working again and more powerful than ever. The template code was originally written by Joseph Wenniger and most of what I show here is still working like originally implemented by him. Still, there were some improvements I would like to show; also, I'm sure many readers might not be aware of this great feature at all.

Classical snippets use case: insert a for loop witout having to type the iterator variable three times.
The template interface, which is part of the long-time stable KTextEditor API, was heavily cleaned up and now just consists of a single function
    bool insertTemplate(const KTextEditor::Cursor& insertPosition,
                        const QString& templateString,
                        const QString& script = QString());
which inserts a template into a view at the given position. It's very easy to use and still powerful -- if you write an application which uses KTextEditor, it might be worth to spend a moment thinking about how you might be able to make use of it.
I also heavily refactored the implementation of the interface. More than 1000 lines of code were removed while effectively enhancing functionality. 

Core functionality changes

I changed the language of the snippets a bit to make it more clear and easy to use. In the following, I want to give a short overview of how it works now.

The heart of the templates (or snippets) are editable fields (shown in green). They are created in the template string by writing ${fieldname}. They can have a default value, which can be any JavaScript expression. Pressing Tab jumps between the fields of a template. Whenever such a field is changed, all so-called dependent fields are updated. Those can simply be mirror fields (created by having a second field with the same name), or can do something which depends on the contents of the other fields in the template, such as perform replacements or concatenations. Again, you can have arbitrary JavaScript expressions doing that.
An example snippet (not very useful in practice) which has three editable fields (find, replace and sample_text) with a default value for each. Changing the values will update the result in the red "dependent" field in real-time.
Noticeable improvements over the previous functionality (from KDE 4 times) is that you can have fields with arbitrarily complicated default values which are still editable, and that the dependent fields can use all other fields as input (not just one like in KDE 4). It is now also possible to have inline JavaScript doing simple operations in the template.

The Shortcuts feature for the snippets now actually works in Kate.

Snippets now also have proper undo; in KDE 4, only a single character typed could be undone at once while editing a snippet. Now, undo grouping works like it always does.

User interface improvements

For easy testing of your snippets, the "Edit Snippet" dialog has a "Test snippet" button now, which lets you test your snippet on-the-fly.
The user interface was simplified by removing unneeded options, and an inline quick-help feature was added which introduces the user to the most important features of the snippet language. Just click the "More" button.
Inline documentation on how snippets work

An example: C++ Header guards

As an example for how this feature works, let's look at how to create a snippet to generate a C++ header guard. First, create a repository for your C++ snippets:
Open the Snippets toolview and click "Add Repository".
Then, enter a name and specify that you want this only for C++ files:
Create your new repository.
Then, add a snippet:
Add a snippet. Easy.

You can retrieve the document's file name from the editor, make it upper-case and replace dots by underscores automatically to get a nice header-guard-suitable format by using code like this:
Example code for how you can create C++ header guards fully automatically.
If you do not want the guard field to be editable, just create a function which does the upper(fileName...) stuff, and have three fields which call the function (like ${func()}) instead of the two mirror fields and one default-valued editable field. If you do that, the template handler will immediately exit and not present any editable fields.
The ${cursor} variable can be used to place the cursor after all fields were filled. When you type something there, the handler will exit.

Click Ok. Now, to use your snippet, either press the shortcut you defined (if any), click it in the snippets toolview, or use code completion:
Snippets appear in code completion.
Result after executing our new header guard script. A sensible default value was selected automatically. Pressing Escape or Alt+Enter will exit the template handler and place the cursor at the point marked with ${cursor} in the template.
That should hopefully equip you with most of the knowledge you need to write your own snippets. If you like, you can use the full kate scripting API to write snippet code -- it for example allows you to retrieve the text in the current selection and similar useful things.

Some more examples on what you can do

Here's a few snippets demonstrating the features of the engine while partly being of debatable practical relevance. I'm sure you can come up with better use cases for some of those things though.
Write a clean regular expression in a comment and have the snippet mirror it with added extra-backslashes and removed spaces in a QRegularExpression variable. Makes regular expressions even more write-only than they already are.
Get the file encoding from the editor and use it as the coding of a python file header.

Some base64 in the selection ...

... decoded by a snippet which takes the selection and inserts the base64-decoded result.

Next steps

My next step will be to make this plugin loadable in KDevelop as well -- which should be quite easily possible due to the awesome work done in kate to make the plugin infrastructure more generic. If you have further ideas on how to improve the snippets, let me know :)

11 comments:

  1. Hi, is there any way how to return ${cursor} variable from javascript ? I need it for snipet where cursor location differs based on selected text.

    ReplyDelete
    Replies
    1. You mean the cursor position in line, column? In principle by using something like ${view.cursorPosition().line()}, but that doesn't seem to work ... apparently the KTextEditor::Cursor class is not exported to JS :/

      Delete
  2. Old and good kate snippets plug-in was able to show some useful comments from , etc.

    I used them intensively to add explanations what each snippet does exactly.

    Now this all is gone, because the only "name" field doesn't allow to add spaces in snippets :(

    This is a HUGE step backward in functionality

    ReplyDelete
    Replies
    1. *from
      -> from displayprefix, displaypostfix

      Delete
  3. We decided to remove this feature, because the dialog was way too crowded and intimidating, and I felt like it was not required. It could be added back in, patches welcome. If you want anyone to notice, please post this on bugs.kde.org as a wish.

    ReplyDelete
    Replies
    1. snippets plugin should give overview of snippets and not only create a list of names. I agree that this is ok if you have about 20 snippets, you can overview all of them. I created library with about 1000 snippets.



      Compare snippets in the list now

      class
      class-1
      class_x_should_use_such_stupid_description_in_the_name


      and before
      class // this is a simple snippet and comment is in displaypostfix

      clas // with some default constructor and this comment is also in displaypostfix

      etc.

      Delete
    2. As said, if you want to discuss this let's do it on bugs.kde.org please and not in my blog. That way other people can participate as well.

      Delete
  4. Fernand VeilleuxMarch 6, 2016 at 9:51 PM

    As I see, you still have not find solution to the bad indentation i.e. when inserting at the 3rd tab from margin.
    As I see it, it does not read the number of spaces/tabs there are before the insert point. If it did and find nothing else but spaces/tabs it should copy those right after inserting a new line then the line to append.
    Then no matter where I would insert a snippet, it would left align with the same column

    What do you think ?

    ReplyDelete
    Replies
    1. Also here, can you please create a bug report and assign it to me? This blog is really not the place to track issues :/

      Delete
  5. I upgraded from Fedora 22 to 23 to 24, and somewhere along the way my snippet repository disappeared from Kate. The file is still on the file system, but I don't know how to load it back into Kate.

    Neither have I worked out how to publish repos yet, but that's less important for now.

    Thanks

    ReplyDelete
    Replies
    1. The way I have found to solve this problem was by:
      - creating a new snippet in Kate with some weird, unique contents (i.e. 'findmysnippet').
      - search my home folder for the new location, using full text search in krusader for .xml files
      - copy the old snippet files to their new location.

      Delete