MACRO(s): Debug JXA from Atom, Finish Debugging JXA from Atom

MACRO(s): Debug JXA from Atom, Finish Debugging JXA from Atom

These are helper macros to make it easier to debug JavaScript for Automation (JXA) scripts when using Atom. You may decide to edit them to change the workflow, but they work (on my machine) as is.

###Assumptions:

  1. You already know how to use Atom for JXA editing. If you don't, I'll (hopefully) be documenting this in a post or wiki entry in the near future.
  2. You already set up Safari for debugging (i.e. you have the "Develop" menu available in Safari, and have "checked" the appropriate submenu item(s) for your computer).

These macros are extremely easy to use.

##Debug JXA from Atom

Debug JXA from Atom.kmmacros (14.2 KB)

###Workflow:

  1. Situation:
  • You've got a JXA script you're editing in Atom, and it already has a file name so "Save" will work without requiring user intervention. NOTE: Saving isn't strictly necessary so if you want to eliminate that step, go ahead).
  • Script Editor is NOT running.
  1. Run this macro.
  2. It starts Script Editor and has it compile and run the script. Opens Safari if needed.
  3. If a "debugger" statement is encountered, or Safari is set up to pause when encountering a JSContext, it will show the debugger.

###Detailed Steps:

  1. You launch this macro.
  2. The Atom script is copied to the clipboard, and the file is saved.
  3. If Script Editor is open, the macro is canceled with an error, so we don't accidentally overwrite an unsaved script.
  4. Script Editor is launched.
  5. SE starts a new document, for JavaScript.
  6. The text copied from Atom is pasted in.
  7. If Safari is not running, it is started, and the "Develop" menu is opened then closed (doesn't seem to work without this step, on my machine).
  8. Back to Script Editor, which compiles the script.
  9. If compile errors, they are displayed and the macro cancels. Everything is easier if you eliminate compile errors before triggering this macro.
  10. SE runs the script.
  11. Assuming a "debugger" statement is hit, or Safari is set up to pause right away when it sees a JSContext, Safari will open the debugger window.

Finish Debugging JXA from Atom

Finish Debugging JXA from Atom.kmmacros (6.5 KB)

Trigger this macro while in the Safari Debugger. It closes the Debugger window, closes Script Editor without saving, and returns to Atom.

I have these 2 macros set to the same hotkey trigger. In Atom, I press F15 to debug. In Safari, I press F15 to quit debugging.

4 Likes

More great macros! :thumbsup:

Thanks, Dan.

One note: It is somewhat amusing, or ironic, that you use AppleScript to control Script Editor for a JXA script. :smile:

Oh, I'd like to highlight your AppleScript that sets the language of Script Editor.
Last I knew, no one had been able to figure this out (but maybe I missed it).

IAC, here's Dan's script tp set SE language:

tell application "Script Editor"
  activate
  set _javaScript to first language whose name is "JavaScript"
  
  set _window to window 1
  if name of _window is not "Untitled" then
    return "Can't find Untitled window"
  end if
  set _doc to document of _window
  set language of _doc to _javaScript
end tell

LOL. Funny. It's because I was using Script Debugger's Dictionary/Explorer, so I was already there. Didn't even think about it. That's probably what people who speak two languages do sometimes - intersperse words from the other language without realizing they did it. :slight_smile:

As for setting the language, it seemed obvious to me. However, I know, as much as anyone, that what's obvious to one person is not necessarily obvious to someone else. Maybe I just got lucky.

I think perhaps you may have :slight_smile:

The SE Application's language collection has always been shown in the library:

Just to be fair, seeing it and realizing how it should be used are two different things entirely. :slight_smile:

Fair enough :slight_smile: here’s another idiom for reaching it – through the document object – in a script which toggles comments in lines selected in Script Editor, detecting the language to decide which comment syntax to use:

// ROBIN TREW 2015 MIT License

// OSX 10.10 SCRIPT EDITOR – TOGGLE COMMENTS

// Add remove '// ' from before printing characters

// Depends on a library .scpt being saved at ~/Library/Script Libraries/DraftsSE.scpt
// See: See: http://support.foldingtext.com/t/writing-scripts-which-run-on-both-foldingtext-and-ios-drafts-4/652/7

// INCLUDE LIBRARY OF IOS DRAFTS-COMPATIBLE EDITOR FUNCTIONS:
function run() {
"use strict";

    // PART 1: CODE SPECIFIC TO YOSEMITE SCRIPT EDITOR:

    // SE implmentations of the basic iOS Drafts functions
    // (Implementations of these functions for FoldingText and other editors with .js scripting at:
    // 
    // getText()                               getSelectedLineRange()
    // setText(string)                         getSelectedRange()
    // getSelectedText()                       setSelectedRange(start, length)
    // setSelectedText(string)                 getClipboard()
    // getTextInRange(start, length)           setClipboard(string)
    // setTextInRange(start, length, string)


    var app = Application("Script Editor"),
        lstDoc = app.documents(),
        oDoc = lstDoc.length ? lstDoc[0] : null,
        strComment, lstSeln;

    if (!oDoc) return false;

    //either var drafts = Library('DraftsSE'); // Assuming file saved at: ~/Library/Script Libraries/DraftsSE.scpt
    // or locally:
    var drafts = {
        getTextInRange: function (iStart, iLength) {
            if (oDoc) return oDoc.text().substring(iStart, iStart + iLength);
        },
        setSelectedRange: function (iStart, iLength) {
            return [iStart, iLength];
            if (oDoc) {
                try {
                    oDoc.selection = oDoc.text.characters.slice(iStart, iStart + iLength);
                } catch (e) {}
            }
        },
        getSelectedLineRange: function () {
            // works, but let me know if you see a shorter route :-)
            var rgxLFCR = /[\r\n]/,
                dct, strFull, iFrom, iTo, lngLast;
            if (oDoc) {
                dct = oDoc.selection.characterRange();
                strFull = oDoc.text();
                if ((typeof strFull)[0] !== 's') return null;
                lngLast = strFull.length - 1;

                iFrom = dct.x;
                iTo = dct.y - 1;
                if ((iFrom < lngLast) || (iTo < lngLast)) {
                    while (iFrom--)
                        if (strFull[iFrom].match(rgxLFCR)) break;
                    iFrom += 1;
                    while (iTo++ < lngLast) {
                        if (strFull[iTo].match(rgxLFCR)) break;
                    }
                    return [iFrom, (iTo - iFrom)];
                } else return null;
            }
        },
        setTextInRange: function (iStart, iLength, strText) {
            if (oDoc) { // record the existing selection coordinates
                var lngDelta = (strText.length - iLength) - 2,
                    oSeln = oDoc.selection,
                    dct = oSeln.characterRange(),
                    iFrom = dct.x,
                    iTo = dct.y;

                try { // use the selection to edit elsewhere, then restore with any adjustment
                    oDoc.selection = oDoc.text.characters.slice(iStart, iStart + iLength);
                    oSeln.contents = strText;
                    oDoc.selection = oDoc.text.characters.slice(
                        (iStart < iFrom) ? iFrom + lngDelta : iFrom, ((iStart + iLength) < iTo) ? iTo + lngDelta : iTo
                    );
                } catch (e) {}
            }
        }
    };

    app.includeStandardAdditions = true;
    
    
    // Javascript '//' or Applescript '--' ?
    strComment = (oDoc.language.id()[0] === 'j') ? '\/\/' : '--';

    // PART 2: CODE COMPATIBLE WITH FOLDINGTEXT, DRAFTS, 1WRITER, TEXTWELL

    function toggleComments(strCommentChars) {
        var lstLinesFromTo = drafts.getSelectedLineRange();
    
        if (lstLinesFromTo === null) return false;
    
        // Javascript '//' or Applescript '--' ( this draft doesn't cover /* */ )
        var rgxComment = new RegExp('^(\\s*)' + strCommentChars + '(\\s?)(\\s*)(?=\\S)', 'gm'),
            rgxLeftSpace = /^(\s*)(?=\S)/,
            rgxAddComment,
            strAddComment,
            iFrom = lstLinesFromTo[0],
            lngSelnChars = lstLinesFromTo[1],
            strLines = drafts.getTextInRange(iFrom, lngSelnChars),
            blnCommented = rgxComment.test(strLines),
            lstLines,
            i, lngChars, oMatch, lngMatch,
            lngMin = Infinity,
            strLine,
            strNew,lngLen;

        if (blnCommented)
            strNew = strLines.replace(rgxComment, '$1$3');
        else {
            // shortest whitespace to left ?
            lstLines = strLines.split(/^/m);
            i = lstLines.length;
            while (i--) {
                strLine = lstLines[i];
                if (strLine) {
                    oMatch = rgxLeftSpace.exec(strLine);
                    if (oMatch) {
                        lngChars = oMatch[1].length;
                        if (lngChars < lngMin) lngMin = lngChars;
                    } else {
                        lngMin = 0;
                        break;
                    }
                }
            }
            if (lngMin === Infinity) lngMin = 0;

            // MAKE A REGEX TO INSERT THE COMMENT PATTERN N SPACE CHARS INTO THE LINE
            // strAddComment = '^(' + Array(lngMin + 1).join('\\s') + ')';
            // rgxAddComment = new RegExp(strAddComment, 'gm');
            // strNew = strLines.replace(rgxAddComment, '$1' + strCommentChars + ' ');
            strNew = strLines.replace(
                new RegExp(
                    '^(' + Array(lngMin + 1).join('\\s') + ')', 'gm'
                ), '$1' + strCommentChars + ' '
            );

        }

        drafts.setTextInRange(iFrom, lngSelnChars-1, strNew);
        lngLen = strNew.length-1;
        drafts.setSelectedRange(iFrom, lngLen);
        return [iFrom, lngLen];
    }

    // MAIN
    lstSeln = toggleComments(strComment);
    // return lstSeln;
    drafts.setSelectedRange(lstSeln[0], lstSeln[1]);
    
}

Once you have done a search for a keyword like language in the scripting library, you can explore the relevant collections and properties in the Safari debugger, or if you happen to have it, in Script Debugger,


You can use Script Debugger to inspect JXA properties?

The OSA scripting dictionaries are the same - language-independent and shared. Script Debugger lacks the Script Editor dictionary browser option of viewing the .sdef entries though language-specific spectacles,

but you can still inspect the same object hierarchies

1 Like

You guys make my head hurt. For any novices reading this stuff, just to be clear, I have no idea what they are talking about and I manage to use Keyboard Maestro perfectly fine :wink:

1 Like

Yep, this topic is entirely about developing JavaScript for Automation (JXA) scripts. It is NOT directly related to Keyboard Maestro, although KM offers the very powerful Execute JavaScript for Automation Action.

So, if you are not into developing JXA scripts, you can mostly ignore this topic.

As I would, if the topic was about, say, a macro for Evernote.

Also, I suspect that anyone who wasn't truly interested in this topic would have stopped reading well before now. :smile: Probably even some who were interested stopped reading by now!

1 Like

Clearly my humour is off today, the topic is fine.

I just find it amusing that all this scripting is so baffling to me. I’m going back to my nice simple Objective-C++ code.

2 Likes

Now, that made me literally laugh out loud.

1 Like

Sorry, I thought I was being funny. I was harkening back to the conversation you guys had about the use of all caps, and I thought this would inject a little levity. Since I'm still laughing, it did for me, anyway. :slight_smile:

And no worries. Although you had be seriously questioning my sanity! :slight_smile:

Curious: JSHint is complaining about "forgotten debugger statement". What do you do about that? Just ignore it?

There’s several things you can do. One of them is to ignore it.

Another is to turn off the warning with a comment, and turn it back on after the statement. Like this:

/*jshint -W087 */
debugger;
/*jshint +W087 */

What do you do about the “undefined” message for ObjC, Application, $, etc.? And are JSLint and JSHint battling each other?

'use strict';   /*jslint node: true */
// suppress warning about functional form of use strict

I don’t remember seeing those, but you can always turn them off.

I should have given you more examples. Turns out disabling JSHint fixes it all — apparently that is no longer maintained. The functional form business is because I had a script (intentionally) lacking an enclosing function.