Status Report – December 2018

Happy New Year, everybody!!

This last month was pretty busy for me and I accomplished quite a bit, as you can see below. One of more interesting pieces is doing a better job of pre-compile error detection and removal of incorrect errors being reported. There is a lot more to be done in this arena and I could probably go on forever creating Lint-like behavior, but I will stop when things start getting esoteric. 🙂 For immediate concerns, though I am working on type issues (e.g. passing/assigning an incorrect type) and will next address detecting fields (and their types) in anonymous structures.

The new example project for building Heaps on HashLink was for a response to a discussion on Heaps.io. Folks have been lamenting that it is difficult to get things moving when you are fighting the tooling and environment, and an unfamiliar IDE just makes it harder. So, having workable examples is very useful. Now we have our second one, available on the github repository. More will be coming.

Fixing “Extract Variable,” “Extract Constant,” and AIR debugging was in response to Patreon supporters. (Thank You for you continued support!) If you would like _your_ issues prioritized, that is a good way to get our attention. Of course, if your company becomes a sponsor, you can be guaranteed that your issues will be addressed.

For you folks who like to build the plugin yourselves, we have moved to a Gradle build, rather than the old Ant builds.  It’s a bit easier, particularly because you can now target any version of IDEA while working in your favorite version.  Just open the project normally and everything should just work.  But, if it doesn’t, we’ve also updated the documentation so you can figure it all out.  (Note that we do most of our work on the ‘develop’ branch, so that’s what you need to get the Gradle build — until our next release, anyway.)

What I worked on for our supporters:
1) Type annotation cleanup: (ongoing – not submitted)
– Detect Float values being assigned to Int variables in variable declarations – including adding selectable quick fixes (such as “Wrap with Std.int()”). (in-progress)
– Downgrade visibility (public/private) incompatibility to a warning. (complete – not reviewed)
– Fix “Float = Int” marked as error. (complete – not reviewed)
– Allow Dynamic as an interface type (as the compiler allows). (complete – not reviewed)
2) Auto-close regions: (reviewed and merged)
– Added functionality to auto-close regions when a file is opened.
– Provided check boxes for auto-closing of the various region types (AS3/C#, FlashDevelop, IDEA styles)
– Provided check box for closing unused paths in conditional compilation (#if/#elseif/#else/#end).

What I worked on for the Community:
1) Merge m0rkeulv’s changes to convert from Ant to Gradle builds of the plugin. (Merged)
2) Fix “Extract Variable” and “Extract Constant” refactorings. (Submitted for review)
– Stop infinite loop when a variable was being extracted to the class level.
– Avoid Haxe keywords when making variable name suggestions.
– Fixed multi-select (for renaming all occurrences simultaneously).
– Fixed semi-colon detection and insertion.
3) Allow Adobe AIR targets to be debugged via the flash debugging system. (Merged)
4) Create HeapsOnHLExample, a working sample project including Heaps and Hashlink. (Merged)
5) Finish new UI for standard library locations. (In-progress)

Community Contributions:
1) m0rkeulv’s build changes were actually from June, but we weren’t ready to take them back then.

Next Month’s Goals:
1) More type annotation cleanup.
2) Finish new UI for standard library locations.
3) Bug hunt. Resolve a number of issues which have come up recently:
– Issues debugging C++ projects when built with Lime. (Currently looks like an hxcpp issue.)
4) Support of some new Haxe4 features.

Have a great month.
-Eric

Status Report: April 2018

Hello everybody,

This month we had a couple of patch releases that fixed a few issues with our 1.0 release. We have 2017.3- and 2018-specific releases available now with the compatibility issues addressed.

What I did for this month for my sponsors:
– CPU over-use debugging (incomplete).

What I did for everybody:
– Build and release scripts for IDEA 2017.3 and 2018.
– Fix source incompatibilities for these versions of IDEA.
– Fix a debugger crash when multiple files with the same base file name exist in the project. (Issue 792)
– Released 1.0.1 and 1.0.2 patches.
– Better error handling when haxelibs are installed incorrectly.

Community contributions this month:
– Fix performance issues with indexing and syntax inspections.
– Add support for ‘final’ syntax introduced in Haxe 4.
– Add support for new function type syntax introduced in Haxe 4.
– Fix support of explicit abstract forwards; now fields and methods that were not forwarded will not be resolved as valid.
– Fixed recognition of standard types for more project configurations.
– Add Haxe SDK setup validation.
– Fix typo in haxelib metadata parser, which was keeping library sub-tree source directories from being found.

On my queue for next month:
– Duplicate and fix the CPU over-use issue on macOS.
– More work toward parsing and syncing project files, leading to the automatic managing for compiler conditional definitions.

Just what is a “selectioner” anyway?

Getting double-clicks to highlight what you want.

I’ve just spent a day and a half fixing bug #212, where the IntelliJ-Haxe plug-in doesn’t appropriately select words inside of string constants (a.k.a. characters enclosed in quotes: “This is a string.”).  Before I fixed it, when you double-clicked on a string constant in the editor, it selected the entire string rather than the word you clicked on.  Most programmers expect the word to be selected and get frustrated.

It turns out that while I was fixing the string handling that the comment handling was broken, too, as seen in this animation:

So, with a little experimentation and a little extra effort, I was able to correct both issues:

Behavior of double-clicking in comments and string constants.

Hopefully, folks will find their double-clicking a bit more predictable.

How it’s done.

From here on in, this post gets quite technical, and is really targeted to plug-in authors. As such it assumes general knowledge of JetBrains’ plug-in architecture.

Now, JetBrains’ openapi, which IDEA uses extensively, has a default action for selection that handles a lot of general cases that are good enough for most programming languages.  Selections are handled by an action handler, and we can override that just by providing an implementation and noting that in the plug-in’s xml descriptor file (we’ll get there in a minute).  However, rewriting the entire action means that the original action selection handlers will no longer be available and/or we would have to duplicate a lot of code.  There’s got to be a better way…

It turns out that the selection action itself uses pluggable “selectioners” to handle the actual selection process.  (While I don’t like the name JetBrains chose, I guess it is easier to type than “SelectionProcessor,” “SelectionCreator,” “SelectionHandler” etc.)  These are much easier to create and they don’t require rewriting the entire selection mechanism.  Here is the API:


public interface ExtendWordSelectionHandler {
  ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.extendWordSelectionHandler");
  boolean canSelect(PsiElement e);
  List select(PsiElement e, CharSequence editorText, int cursorOffset, Editor editor);
}

The first member is the name of the extension point, which we will use in the plugin’s xml descriptor (src/META-INF/plugin.xml in our tree). The second member, the canSelect method, determines whether this handler can create a selection using given PSI element. The third member, select, actually calculates the selection.

When IDEA determines that it’s time to do a word selection, it goes down the list of *ALL* installed selection handlers.  It calls each of their canSelect methods and remembers which of them had positive responses.  Then, it calls the select method on each of those, and compares each of their results.  The result with the smallest specific selection is kept.  The code is in com.intellij.codeInsight.editorActions.SelectWordUtil.processElement(). Basically: (code paraphrased for clarity)


    for (ExtendWordSelectionHandler selectioner : extendWordSelectionHandlers) {
      if (selectioner.canSelect(element)) {
        availableSelectioners.add(selectioner);
      }
    }
    for (ExtendWordSelectionHandler selectioner : availableSelectioners) {
      List ranges = selectioner.select(element, text, cursorOffset, editor);
      for (TextRange range : ranges) {
        if (minimumRange.get().contains(range)) {
          minimumRange.set(range);
        }
      }
    }

What this means is two-fold: It is up to the handler to decide whether it can work with the type, but it CANNOT guarantee that it is the only handler that will create a selection. Nor can it guarantee that its result will be the one used; the result competes with all others to be the most specific selection. If you want to fully control the selection process, you will have to override the editor action.

At any rate, you can see our solution in the github repository. As you can see, we first be sure that our Haxe-specific handler will not interfere with other languages:


  public boolean canSelect(PsiElement e) {
    return (e instanceof HaxePsiToken   // <---- This line implies that it's a Haxe language token.
            && e.getLanguage().equals(HaxeLanguage.INSTANCE)  // <---- This line ensures that another language hasn't subclassed our type.
            && HaxeTokenTypes.REGULAR_STRING_PART.equals(((HaxePsiToken)e).getTokenType()));  // <---- And this one ensures that we're only working with strings.
}

Then we implement the actual selection algorithm. I don’t duplicate that code because it’s a bit lengthy, but you can find it here. It might be easier to look at it in the Pull Request. Then you’ll be able to see the code used to determine the bounds of the word.

At any rate, we are given the position of the cursor, the PSI element at that position, the (entire) text of the buffer in the editor, and a pointer to the editor window itself. From these, we start at the given position, get the text for the element, and walk backward and forward looking for word termination character. If the position is on a space and a word ends on the prior position, then we’ll select that word.

The last thing that we have to do is register the new handler. This is done in the plugin.xml file. We only had to specify the extension point name and the implementing class:


   <extensions defaultExtensionNs="com.intellij">
    [...]
    <extendWordSelectionHandler implementation="com.intellij.plugins.haxe.editor.actions.wordSelection.HaxeStringSelectioner" />
    [...]
   </extensions>

Note that the entire name for the extension point as given in the API is com.intellij.extendWordSelectionHandler. However, the defaultExtensionNs does not have to be respecified on the actual implementation line. (However, if you place the directive in an extensions section that uses a different default, then you need to specify the whole string. I haven’t tried this, myself.)