Big update to prettier-extension-monkeyc

I've posted about prettier-extension-monkeyc before, but I've added a bunch of new features that developers will probably like (well, I've been missing them, so maybe you have too).

The new features it implements for VSCode include:

  • Goto Definition. Point at a symbol, Ctrl/Cmd click, and it will take you to the definition. Or F12
  • Goto References. Right click on a symbol and select "Goto References". It will show you all the references. Or Shift-F12
  • Peek Definition/Peek References. Same as above, but in a popup window so you don't lose your place in the original document.
  • Rename Symbol. Right click on a local, function, class or module name, and select "Rename Symbol". It will rename all the references. It doesn't yet work for class members/methods.
  • Goto Symbol. Type Ctrl/Cmd-Shift-O and pick a symbol from the drop down (which has a hierarchical view of all symbols in the current file). This also appears as an outline across the top of the file.
  • Open Symbol By Name. Type Ctrl/Cmd-T, then start typing letters from a symbol name. A drop down will be populated with all matching symbols from anywhere in your project.

Older features include a prettier based formatter for monkeyc, and a monkeyc optimizer that will build/run/export an optimized version of your project.

[edit: My last couple of replies seem to have just disappeared, and the whole conversation seems to be in a jumbled order, so tldr: there's a new test-release at https://github.com/markw65/prettier-extension-monkeyc/releases/tag/v2.0.9 which seems to work for me on linux. I'll do more verification tomorrow, and push a proper update to the vscode store once I'm sure everything is working]

  • OK, thank you very much!

    One more related question, if you please. Do you know, what is special about exception classes lookup in "catch" (or it is not because of "catch", but because of "instanceof")?

    import Toybox.Graphics;
    
    class SomeClass {
        function createBitmap() as BufferedBitmap? {
            try {
                return Graphics.createBufferedBitmap({ :width => 10000, :height => 10000 }).get() as BufferedBitmap;
            } catch (e instanceof Graphics.OutOfGraphicsMemoryException) {
                return null;
            }
        }
    }

    This code builds and works (it returns null, as bitmap is too large), but with "e instanceof OutOfGraphicsMemoryException" instead of "e instanceof Graphics.OutOfGraphicsMemoryException" there is "Cannot find symbol ':OutOfGraphicsMemoryException' on type 'self'." despite having "import Toybox.Graphics", and it fails in runtime with "Symbol Not Found Error".

  • Do you know, what is special about exception classes lookup in "catch"

    That's a new one. My first thought was that Garmin's unwinder has very little information about what its unwinding, and so the class gets looked up in the global scope. But then I remembered that the instanceof check actually runs in the context of the original function, after the unwinder has finished (or at least, it's finished for now). So I would expect this lookup to obey the same rules as anything else.

    How did you invoke createBitmap()? If you invoked it on an instance of SomeClass (as you should, since its a non-static method), then I really don't understand the behavior (I'll see if I can repro when I get a chance). But if you invoked it as SomeClass.createBitmap() then maybe? Although even then, because of the import, I would expect it to work, unless you invoked it in the context of some unrelated class (I mentioned this in a previous post - but Garmin's type checker should have complained about that).

    [Edit: The above isn't quite right. I was incorrectly thinking that because this is a non-static member function it should find "OutOfGraphicsMemoryException" when it does the lookup in the Object superclass (as described in my previous post). But that's not the case. It would find Graphics via that lookup, but won't find anything inside the Graphics module without a Graphics qualifier]

  • In this example I create a new instance of "SomeClass" and call it from non-static function of main app class.

    In real project I have such static and non-static functions with try-catch, and I haven't noticed a difference, whether such code is in static function or not, or is called from another static function or not (I haven't checked non-static function call from static function of the same class, though).

  • I took a look at the generated code, and I'm pretty sure it's a compiler bug. At best its a "feature", but I can't think what they're trying to achieve if that's the case.

    I modified your example a little:

    import Toybox.Graphics;
    
    var obj as Toybox.Lang.Object = 1;
    (:typecheck(false))
    class SomeClass {
        function createBitmap() as BufferedBitmap? {
            var ex = obj instanceof OutOfGraphicsMemoryException;
            if (!ex) {
                try {
                    return (
                        Graphics.createBufferedBitmap({
                            :width => 10000,
                            :height => 10000,
                        }).get() as BufferedBitmap
                    );
                } catch (e instanceof OutOfGraphicsMemoryException) {
                    ex = obj instanceof OutOfGraphicsMemoryException;
                    return null;
                }
            }
            return null;
        }
    }
    

    The lookup of OutOfGraphicsMemoryException in the first and third instanceof gets emitted as

        getmv Toybox_Graphics OutOfGraphicsMemoryException

    This is what you would expect, since you imported Toybox.Graphics.

    But the second lookup (the one in the catch clause) gets emitted as 

        getselfv OutOfGraphicsMemoryException

    This is trying to lookup OutOfGraphicsMemoryException in the context of SomeClass, which is going to fail (even after the fallback to looking it up in the context of Object).

    Note that "using" and "import" are not runtime constructs, they're compile time. If the compiler sees "import Toybox.Graphics" then references to Graphics are translated as "getm Toybox_Graphics" (usually combined with a lookup of something, so with newer devices it becomes getmv), rather than "getselfv Graphics". And references to classes and modules inside Graphics are translated as "getmv Toybox_Graphics Thing".

    Since the compiler didn't apply that transform inside the catch expression, it doesn't work at runtime - and I guess the type checker recognized that it wasn't going to work. But it seems to be the compiler's fault; it should work, the compiler just isn't translating it correctly.

    One last thing - with a lot of exceptions, this wouldn't have been a problem. Any exception defined in Toybox.Lang (and there are quite a few), would have worked. "getselfv OperationNotAllowedException" would actually find the exception, because of the search through Object. So although the generated code would be less efficient, it would still work, which is maybe why the bug hasn't been noticed before.

  • Understood. Thank you!

  • I'm not sure if this is something that is related to prettier or can be done, but I'll try my luck. There's some annoying thing, that seems to be caused because of the directory structure in one of my apps:

    root/
    root/sources/
    root/sources/App.mc
    root/AppA/
    root/AppA/manifest.xml
    root/AppA/monkey.jungle
    root/AppA/sources => ../sources

    root/AppB/manifest.xml
    root/AppB/monkey.jungle
    root/AppB/sources => ../sources

    In other words I have 2 apps that are almost identical. Their differences are mostly in annotations (from their own monkey.jungle) and the appId in the manifest. all the sources are identical, so the whole sources directory is a softlink (I'm on Mac)

    root is the root of my git repo. In VSC I open root/AppA/ as a directory and work inside. When I open a file from the file explorer I see it as source/App.mc and all the hints, references, etc that Prettier Optimizer gives work perfectly.

    However when I have a crash, I see this in the terminal:
    Stack:
    - foo() at /Users/me/garmin/root/source/App.mc:43 0x10000189

    And when I click on that then a new tab is opened with App.mc, but I see it's path as /Users/me/garmin/root/source/App.mc (instead of source/App.mc) Now I don't know if this could be somehow fixed (if yes, then it would be the best I guess), or if Prettier can somehow intercept the click and lead me to the already open [AppA/]source/App.mc (this would be 2nd best). And if none of these can be done, then I hope that at least the the newly open tab with /Users/me/garmin/root/source/App.mc in it can be improved so that the definitions, references work there.

    Also if none of the above is possible but someone knows a better way to organize the files and work in either AppA or AppB in VSC I'm also eager to hear it.

  • I'll take a look at what might be possible in the extension, but my recommendation would be to avoid soft links if at all possible.

    Couldn't you just put base.sourcePath = ../sources in your monkey.jungle files?

    [Edit: if the objection is that now Search doesn't look in ../sources by default, you could always setup a workspace with both root, and root/AppA in it. Or even root, root/AppA and root/AppB, or if you really want to keep the two projects separate, root/sources and root/AppA]

  • In windows I get around it by layout out the tree and jungle file like this:

    project.manifest = wfSource/manifest.xml
    
    #appType = ../appSource
    appType = ../wfSource
    
    srcBase = ../source
    rezBase = ../resources
    
    base.sourcePath = $(srcBase);$(appType)
    base.resourcePath = $(rezBase
    

    It's not perfect, variables aren't allowed in the manifest path so I have to update the hardcoded path and appType when I switch. Also learned the hard way that paths in the jungle are relative to the directory where the manifest file is.

  • I have other things that complicate it, not only sources. For example I don't use resources*, instead I have:

    most of them are symlinked from root/AppA/features/feature1 -> ../../features/feature1, but some of them are actual files (i.e for AppName)

    root/AppA/features/base/lang-eng/strings-name.xml - here I have AppName=AppA
    root/AppB/features/base/lang-eng/strings-name.xml - here I have AppName=AppB

    others are symlynked from:

    root/features/feature1/
    root/features/feature1/lang-eng/strings.xml
    root/features/feature1/lang-hun/strings.xml
    root/features/feature1/resources/fitcontributions.xml
    root/features/feature1/resources/properties.xml
    root/features/feature1/settings/10/10-f1-settings.xml
    ...
    root/features/feature2/settings/20/20-f2-settings.xml
    ...
    root/features/feature3/settings/11/11-f3-settings.xml

    The python script generates the lang and resourcePath for each device depending on configuration in monkey.jungle and device capabilities and constraints in monkey.jungle

    Maybe I need to attack this from another direction and add this multi-app feature to my monkey-generator.py...

  • Maybe I need to attack this from another direction and add this multi-app feature to my monkey-generator.py

    Yes - it sounds to me like the solution is to create a code-workspace for each app. Each code-workspace would contain root, and the app (or, as I said before, you could just combine everything and put root and both apps in a single code-workspace). And then modify your generator to not use symlinks.