Our Blog

Let us find tech solutions together

Apr 23

Humans love shiny. And there’s nothing more shiny to a programmer than a brand new version of a tool used every day. Here at Mystic we use IRC as our primary mode of communication and project collaboration, and several of us have chosen Weechat. Recently we wrote about receiving irc notifications from Weechat over ssh, and the plugin written was for the current stable version of Weechat 0.2.6. The development version of Weechat is a complete rewrite, and breaks plugin compatibility. If we rewrote growl-net-notify.pl for this new version, the choice was to manage two separate source files, or find some way to make one work for both. I chose the latter method because programmers as I’ve always said, are smart lazy people, we’ll do whatever we can to automate something.

Identifying Weechat version

First step in combining the version code is identifying which version we’re running under. Since I don’t want to cause any errors in either version when the plugin is loaded, I decided to use Perl’s exists method which can be used to check existence of a subroutine:

if(exists &weechat::info_get)

With this in hand, we can perform the setup commands for 0.2.6 and 0.2.7 independent of each other, and preset the $weechat_version variable so we can key off of it in other portions of the code.

Changes in 0.2.7

Notifications to the user are prevalent throughout our code, we have several easy commands so the user can easily setup their network settings and parameters that are important. So we use weechat::print a lot, and it has changed in 0.2.7.

The previous version gave you a single parameter for weechat::print, and output to whatever your active buffer was. The new version forces you to specify a buffer, and allows you to pass in an empty string to denote the main buffer.

weechat::print(message) becomes weechat::print(buffer, message)

The developers have standardized on different naming conventions for 0.2.7, and a lot of action oriented items are hook’s. So instead of weechat::add_command_handler, you would write weechat::hook_command. There were some subtle parameter changes as well, and since 0.2.7 is not final, this still might change.

weechat::register has also seen some minor changes, including parameters for author, and license type.

Purely cosmetic changes it seems, weechat::get_plugin_config becomes weechat::config_get_plugin, and weechat::set_plugin_config becomes weechat::config_set_plugin.

The status constants have also changed, so weechat::PLUGIN_RC_OK becomes weechat::WEECHAT_RC_OK, and weechat::PLUGIN_RC_KO becomes weechat::WEECHAT_RC_ERROR.

Message Handling

The majority of the work done in upgrading for 0.2.7 came at the hands of the public message highlights, and the private message highlights. In the previous version, we used Parse::IRC to pull out the pieces of the IRC message that were important to us to show in our notification. In 0.2.7, they’ve simplified things for us greatly.

weechat::hook_print allows us to watch each message, check if our nick has been highlighted, and process accordingly. We’re passed in a nice array which gives us if we’re highlighted, who said it, and the message.

weechat::hook_signal allows us to watch private messages, and gives us a string separated by a tab with the nick that is private messaging, and the message. We might have liked it if we got a nice fancy array passed in just like hook_print gives us, but this works out just fine.

Because we were writing this for both 0.2.6 and 0.2.7, I wanted to get rid of the dependency on Parse::IRC and put a little bit of Perl regex magic to work. Together armed with RegexTester.com, I was able to pull out the data needed in these legacy methods and achieve detachment from needing Parse::IRC.

Now I’m sure there are some Perl-style tricks that I’m not doing here, and the code definitely fails because it is readable :). And of course, after talking with FlashCode on #weechat, he suggested we continue development only on 0.2.7, so the version in source control has two versions now. Feel free to look at older commits to see the combined version, it was definitely fun to figure out how to write it.

If you’d just like to use this plugin for your Growl notification over SSH needs:

Download the plugin

If you’re also interested in reviewing the source:

GitHub repository here

Read more
Apr 22

Our initial plugin for IDEA that talked to MysticPaste.com was fairly simple, offered no feedback to the user that anything happened. I spent some time trying to find out a method of achieving unobtrusive notification that the paste was successful. On initial glance at the API, nothing jumped out except for Messages, and StatusBar.

First crack was using Messages:

1
    Messages.showInfoMessage("Paste successful!","URL automatically copied to your clipboard");

It meets the standard of showing feedback to the end-user, but its definitely obtrusive. The user is forced to click on the OK button, its less of a status and more of a confirmation.

Second crack was using StatusBar. Only thing found in Google is for version 5.0 of the plugin API, and it only has one method, setInfo(String s). Doesn’t look very promising. Introspection though, proves that nothing is as it seems. Through some sleuth googling, downloading some plugins that showed the notifications we like, and googling more, a solution was found.

Adding a Project Component

There’s only one way to get at a StatusBar instance, and that’s through the WindowManager. And there isn’t a global StatusBar, there’s one per Project. So where we got by without a ProjectComponent previously, we need one now. The following snippet gives us a StatusBar object:

1
    StatusBar statusBar = WindowManager.getInstance().getStatusBar(project);

And from that, we can call fireNotificationPopup with the proper parameters, to show our message. If you happen to find the JavaDoc, congratulations, initial searches came up empty on this method, but it works great. Here’s the entire method for the notification, which we add to the event thread so Swing can fire and show it whenever it makes sense:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    private Project project;

    public MysticPasteIndicationComponent(Project inProject) {
        project = inProject;
    }

    ...

    protected void updateWithStatus(final String statusMessage) {

        /**
         * Don't make Swing angry.  You won't like it when its angry
         *
         */
        ApplicationManager.getApplication().invokeLater(
                new Runnable() {
                    public void run() {
                        JTextArea area = new JTextArea();
                        area.setOpaque(false);
                        area.setEditable(false);
                        StringBuffer notification = new StringBuffer(statusMessage);
                        area.setText(notification.toString());
                        StatusBar statusBar = WindowManager.getInstance().getStatusBar(project);
                        if (statusBar != null)

                        {
                            statusBar.fireNotificationPopup(area, LightColors.GREEN);
                        }
                    }

                }
        );

    }

And now all we need is to call this after the paste was successful. So similar to how we grabbed the Editor, we’ll again pull out our DataContext object bag of tricks, and find our ProjectComponent instance, and fire off our status update as you can see here:

1
2
3
    Project project = DataKeys.PROJECT.getData(context);
    MysticPasteIndicationComponent mpiComponent = project.getComponent(MysticPasteIndicationComponent.class);
    mpiComponent.updateWithStatus("Paste successful!nnURL automatically copied to your clipboard");

That’s it. And we promise, no animals were hurt during this exercise. And DO try this at home.

Read more
Apr 21

From the beginning of building out Mystic Paste, the vision was to have multiple interfaces to allow ubiquitous use of the pastebin. With the variety of plugins we’ve got out now including Eclipse, IDEA, VIM, and NetBeans, that vision is definitely here. If you reached this link looking for the plugin, look no further, the plugin page has downloads for multiple IDE platforms.

For the IDEA plugin we’ll be showing how it was developed, so we can remove the 8+ step process of pasting into a simple 3-step process … select, send, paste.

Setting up the project

With IntelliJ IDEA 8.x getting a new plugin project setup couldn’t be easier! Launch IDEA and choose File -> New Project… -> Create project from scratch

Picture 2.png

Just as with any project, give it a good name, and for type, select “Plugin Module”. Go through the normal project setup details and click Finish when done.

Plugin configuration

There is a simple plugin.xml file that governs how IDEA will interact with your masterpiece. The use case for this simple plugin is to capture text selected in an editor and provide an action hook for the right-click menu. I’ve included the entire plugin.xml file for the mystic paste idea plugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<idea-plugin version="2">
    <name>MysticPastePlugin</name>
    <description>
        <![CDATA[
        This plugin takes a selection from the IDEA editor, and posts it to http://mysticpaste.com.  It then provides the URL in the clipboard
      ]]>
    </description>
    <version>0.2.1</version>
    <vendor url="http://www.mysticcoders.com">Mystic Coders, LLC</vendor>
    <idea-version since-build="8000"/>

    <change-notes>
        <![CDATA[

            <ul>
                <li> Added the notification on the bottom right alerting the user that the paste action was successful</li>
            </ul>


        ]]>
    </change-notes>

    <project-components>
        <component>
            <implementation-class>com.mysticcoders.mysticpaste.plugin.MysticPasteIndicationComponent
            </implementation-class>
        </component>
    </project-components>

    <actions>
        <action id="com.mysticcoders.mysticpaste.plugin.PastebinAction"
                class="com.mysticcoders.mysticpaste.plugin.PastebinAction" text="Add to Mystic Paste..."
                description="Sends the selection to MysticPaste.com">
            <add-to-group group-id="EditorPopupMenu" anchor="last"/>
        </action>
    </actions>

</idea-plugin>

The top portion of this file is all identification information for the plugin, version info, vendor, which idea minimum IDEA version it will work with, changelog details. The next 3 elements define:

  • Application Components
  • Project Components
  • Actions

For a lot more in-depth information on plugin development, the Plugin Development FAQ is very useful.

Application Component

A plugin has many different scopes it can be contained in, and if you’re plugin is not project specific, ApplicationComponent scope is a great place to define it. These components are only loaded once when the IDE starts up.

Project Component

This component is scoped to be project-specific, so IDEA will make an instance of this plugin available for each open project.

Actions

AnAction is used for any code that needs to hook into IDEA’s menus and/or toolbars. This is precisely where we’ll hook into the right-click menu available to use from the editor, and act upon selected code. In here we define the following:

  • Unique id for our action
  • Implementation class
  • Text to show in the popup menu
  • Description which will show in the status bar
  • Element add-to-group which defines what popup menu to attach to, and where to anchor it in the list

Check out IDEA plugin structure for much more info.

Implementation

Our plugin is really simple, we need to take the current editor selection, open an HTTP connection with MysticPaste.com’s plugin servlet, POST the selection text and place the URL in the clipboard. Let’s start with getting the editor selection!

1
2
3
4
5
6
7
8
9
10
11
        DataContext context = event.getDataContext();
        Editor editor = DataKeys.EDITOR.getData(context);

        String selectedText = null;
        SelectionModel selection = null;
        if (editor != null) {
            selection = editor.getSelectionModel();
            if (selection.hasSelection()) {
                selectedText = selection.getSelectedText();
            }
        }

As you can see from the code snippet, we grab an instance of the Editor using the DataContext which can be retrieved from the AnActionEvent instance passed to every action. From there we get the Editor’s SelectionModel, find out if there is indeed a selection, and fill selectedText with the contents.

1
2
3
4
5
6
    Document doc = editor.getDocument();
    VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(doc);
    String extension = null;
    if (virtualFile.getName().lastIndexOf(".") != -1) {
        extension = virtualFile.getName().substring(virtualFile.getName().lastIndexOf(".") + 1, virtualFile.getName().length());
    }

Next up, we want the file extension of the document we’ve grabbed our selected text. We get an instance of VirtualFile through the Document, substring it to grab what should be the extension, and pass it to the sendPaste method along with the selectedText.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    private String sendPaste(String text, String extension) {

        String pasteNumber = null;

        try {
            // Construct data
            String data = URLEncoder.encode("content", "UTF-8") + "=" + URLEncoder.encode(text, "UTF-8");

            if(extension != null) {
                data += "&" + URLEncoder.encode("fileExt", "UTF-8") + "=" + URLEncoder.encode(extension, "UTF-8");
            }

            // Send data
            URL url = new URL("http://www.mysticpaste.com/servlet/plugin");
            URLConnection conn = url.openConnection();
            conn.setDoOutput(true);
            OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
            wr.write(data);
            wr.flush();

            // Get the response
            BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = rd.readLine()) != null) {
                pasteNumber = line;
            }

            wr.close();
            rd.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return (pasteNumber != null ? "http://www.mysticpaste.com/view/" + pasteNumber : null);
    }

If you’re looking to integrate another platform with MysticPaste.com, the code above should provide you all you need. We very simply encode the data for HTTP POST, and use the standard java.net classes to achieve our pasting. The result of this class is a URL in which this paste can be found.

1
    CopyPasteManager.getInstance().setContents(new StringSelection(text));

After a quick null check, we use IDEA’s supplied CopyPasteManager to set the contents to our paste URL.

You can test your app by running it like you would anything else, IDEA provides a target in the Run/Debug configurations to achieve this.

Picture 3.png

IDEA will launch a completely new version of the IDE in which to test your plugin, I would heartily suggest that you create a simple project that can exercise whatever functionality is intended to be used with your plugin.

Deployment

Picture 4.png

If you’re happy with your debugging efforts, and the functionality works for your needs, under the Build menu select “Prepare Plugin Module …” and it will build a plugin jar that you can drop into IDEA’s plugin directory

  • Windows: C:Documents and Settings<username>.IntelliJIdea80configplugins
  • OS X: $HOME/Library/Application Support/IntelliJIDEA80
  • Linux/Unix: $HOME/.IntelliJIdea80/config/plugins

That’s it. Overall the process isn’t too bad once you find all the documentation, grab a few code samples, possibly reverse engineer a few to see how they did it. Have fun, and if you have any questions, or want us to develop a custom IDEA plugin for your organization, contact us.

Read more