Need JavaScript help

I’m feeling especially slow-minded today, and I’ve had enough of trying to decipher puzzles this week. Seems every time I turn around and try to do something “simple”, it’s 5 hours later and I haven’t accomplished anything.

So I’m asking for help.

I want to do the following in JavaScript (JXA, really).

Given a string, and a starting position:

Function 1:

  1. Does the character at the position match regex “[\w]”?
  2. If yes, move forward in the string to find the index of the first non-matching character.
  3. If no, move forward in the string to find the index of the first matching character.

Function 2:

  1. Does the character at the position match regex “[\w]”?
  2. If yes, move backwards in the string to find the index of the first non-matching character.
  3. If no, move backwards in the string to find the index of the first matching character.

Please don’t optimize too much for this specific regex pattern, because I need to do other patterns also. This is just a template to get me started. I’ll use these functions as stated, but I have more I want to do also.

Thanks.

For bonus points, talk to Xcode and:

  1. Get the “selected character range” of the “front text document”, or a document with a specific name.
  2. Get the “contents” (the text) of the" front text document"
  3. Set the selected character range.

As I said, the only reason I’m asking is my brain is out of gas, and I’m plum tuckered out. This has been one of the longest weeks of my life. (Pity party time!!).

Dan,

Sorry, I don’t have an immediate answer for you, but some suggestions:

  • For JavaScript questions, I usually just google it first. Very often, I can find the answer, or something close enough.
  • If that doesn’t work, then I post on stackoverflow.com. There is a tough crowd there, but lots of experts. So if you can ignore the BS, you will usually get a decent answer.

If you are relatively new to JavaScript, then you may find this course helpful. I started it a while back, but got sidetracked on other stuff. Time for me to get back on it.

Javascript & jQuery Front-End Developer Bundle

It looks pretty decent, and is only $29.

And, JIC you haven’t see this, a key reference:
JavaScript Ref Guide, Tutorials, & Documentation – Mozilla

Here’s an indicative first sketch – rough and untested for you to polish up.

Two functions, each of which takes a string and a zero-based integer index, and is intended to return an option value (either an integer index, or undefined).

(function () {
    'use strict';

    // forward :: String -> Integer -> maybe Integer
    function forward(s, i) {
        var iLast = s.length - 1;

        if (i < iLast) {
            var rgxLatin = /\w/,
                rgxOther = /\W/,

                m = (rgxLatin.test(s[i]) ? rgxOther : rgxLatin)
                .exec(s.slice(i));

            return (m ? i + m.index : undefined);
        } else {
            return undefined;
        }
    }


    // back :: String -> Integer -> maybe Integer
    function back(s, i) {
        if (i > 0) {
            var iLast = s.length - 1,
                strReversed = s.split('')
                .reverse()
                .join(''),
                iReversed = iLast - i;

            var maybeNext = forward(strReversed, iReversed);

            return maybeNext !== undefined ? (
                iLast - maybeNext
            ) : undefined;
        } else {
            return undefined;
        }
    }

})();

Thanks! "forward" appears to be working correctly, but "back" is off, and I can't quite figure out why. I'm a programmer - I ought to be able to figure this out. But dang, I just can't. This is where, if I was at work, I'd call another developer over to my desk and show him, and he'd go "it's right there, dummy!"

The problem causes 2 symptoms in my example. One is that the selection is too long by 1 character. But I believe the second symptom is the issue - if it gets solved, the first goes away. I'm telling you this so the pics I'm about to show you will make sense.

Given this line in Xcode, you can see where the caret is:

This is what happens when I go "back":

And if I go back again:

Here's my code. Ignore the test crap. And I don't know why the tabs expand so much here, but whatever:

(function Run() {
	'use strict';
	
var _xcode = Application("Xcode");
var _document = _xcode.sourceDocuments.byName("TestTarget.swift");
var _text = _document.text();
var _currentSelection = _document.selectedCharacterRange();

//var _endPoint = forward(_text, _currentSelection[0]);
//if (_endPoint < 0) return _endPoint;
//_document.selectedCharacterRange = [_currentSelection[0], _endPoint];

var _endPoint = back(_text, _currentSelection[0]);
if (_endPoint < 0) return _endPoint;
_document.selectedCharacterRange = [_endPoint, _currentSelection[0]];

return _endPoint;


function forward(s, i) {
	var iLast = s.length - 1;

	if (i < iLast) {
		var rgxLatin = /\w/,
			rgxOther = /\W/,

			m = (rgxLatin.test(s[i]) ? rgxOther : rgxLatin)
			.exec(s.slice(i));

		return (m ? i + m.index : -1);
	} else {
		return -2;
	}
}

function back(s, i) {
	if (i > 0) {
		var iLast = s.length - 1,
			strReversed = s.split('')
			.reverse()
			.join(''),
			iReversed = iLast - i;
		
		var maybeNext = forward(strReversed, iReversed);

		return maybeNext >= 0 ? (
			iLast - maybeNext
		) : -1;
	} else {
		return -2;
	}
}


})();

First thing, I notice that you’ve replaced the nil (undefined) value in my first code with negative integers - doesn’t that risk a type confusion ? (e.g. the risk of an an undefined result being read as an index into a string, including when back calls forward, for example)

Second, I guess you have checked zero-indexing against 1 indexing ?

Third, can you give me a test string and test index for which back() is not returning the index you were expecting ?

PS re tabs - I think these Markdown processors respond better to 4 spaces :slight_smile:

I changed them to negative numbers so I could tell what wasn't working, when it wasn't working, if you know what I mean. I test the result before I go on, just like I would test for null/nil/missing value/whatever. :slight_smile:

Since it works going forward, that doesn't make sense to me, but anything's possible.

That's actually a good idea - to separate the generic string searches from the way I'm actually using them.

Let me try some things and I'll get back. Got some other stuff to take care of also.

Thanks!

Took another quick look at XCode. I guess you’ve noticed that the selection start is 1-indexed, whereas the selection end is 0-indexed.

// iFrom is 1 indexed
// iTo is 0 indexed 

// e.g.
// [1, 0] -> collapsed cursor at start of buffer
// [1, 1] -> first character only selected
// [1, 2] -> first two characters selected
// [2, 3] -> second and third character selected

Yeah, sorry I haven’t responded back. Yeah, I figured that out, and I believe it’s working now.

The reason I haven’t responded back is that I wanted to create a macro that actually used it, the way I wanted to use it, before I opened my mouth (figuratively) and said “it works”.

Then I ran into another issue. Near as I can tell, I can’t determine the front text document in Xcode - meaning the one that has keyboard focus, whatever. I thought “front text document” worked in AS, but it doesn’t. The only way I can think to do it now is to get the name of the front window, and find the text document for that window, but this will only work if the document is opened in only one window.

I’d love it if you pointed me to something obvious I’m overlooking!

Thanks for your work. Oh, the other thing I was going to test was what happens if there’s an Emoji or something like that in the text. I’m not sure what affects the grapheme cluster stuff, and how that might affect character indexes in JS. Swift seems really concerned with this issue, and makes working with individual characters in a string a total PITA. So I wasn’t sure about JS.

Have to cook now, but here is one rough sketch of a simplest case (though I’m not sure if I’ve looked carefully enough at what you are after):

Moving backwards with a simple collapsed cursor:

(function () {
    'use strict';

    // collapsedCursorToNextBoundary :: Document -> Regex -> Regex -> IO ()
    function collapsedCursorToNextBoundary(d, rgxPattern, rgxOther) {
        var lstFromTo = d.selectedCharacterRange(),

            // NB: 1-indexed
            iFrom = lstFromTo[0],
            iFromZeroed = iFrom - 1,

            maybeZeroIndexedNext = forward(
                d.contents(), iFromZeroed, rgxPattern, rgxOther
            );

        if (!isNaN(maybeZeroIndexedNext)) {
            // From is 1-indexed, To is 0-indexed
            d.selectedCharacterRange = [maybeZeroIndexedNext + 1,
                maybeZeroIndexedNext]
        }
    }

    // collapsedCursorToPrevBoundary :: Document -> Regex -> Regex -> IO ()
    function collapsedCursorToPrevBoundary(d, rgxPattern, rgxOther) {

        var lstFromTo = d.selectedCharacterRange(),

            // NB: 1-indexed
            iFrom = lstFromTo[0],
            iFromZeroed = iFrom - 1,

            maybeZeroIndexedNext = back(
                d.contents(), iFromZeroed -1, rgxOther, rgxPattern
            );

        if (!isNaN(maybeZeroIndexedNext)) {
            // From is 1-indexed, To is 0-indexed
            d.selectedCharacterRange = [maybeZeroIndexedNext + 2,
                maybeZeroIndexedNext]
        }
    }

    // forward :: String -> Integer -> Regex -> Regex -> maybe Integer
    function forward(s, i, rgxPattern, rgxOther) {
        var iLast = s.length - 1;

        if (i < iLast) {
                var m = (rgxPattern.test(s[i]) ? rgxOther : rgxPattern)
                .exec(s.slice(i));

            return (m ? i + m.index : undefined);
        } else {
            return undefined;
        }
    }

    // back :: String -> Integer -> Regex -> Regex -> maybe Integer
    function back(s, i, rgxPattern, rgxOther) {
        if (i > 0) {
            var iLast = s.length - 1,
                strReversed = s.split('')
                .reverse()
                .join(''),
                iReversed = iLast - i;

            var maybeNext = forward(
                strReversed, iReversed, rgxOther, rgxPattern
            );

            return maybeNext !== undefined ? (
                iLast - maybeNext
            ) : undefined;
        } else {
            return undefined;
        }
    }

    var xc = Application('XCode'),
        ds = xc.sourceDocuments,
        d = ds.length ? ds[0] : undefined;

    if (d !== undefined) {
        var lstFromTo = d.selectedCharacterRange(),
            iFrom = lstFromTo[0],
            iTo = lstFromTo[1],
            blnCollapsed = iTo < iFrom;

        // iFrom is 1 indexed
        // iTo is 0 indexed

        // e.g.
        // [1, 0] -> collapsed cursor at start of buffer
        // [1, 1] -> first character only selected
        // [1, 2] -> first two characters selected
        // [2, 3] -> second and third character selected

        xc.activate();
        return collapsedCursorToNextBoundary(d, /\w/, /\W/);
    }
})();

Totally lost me. What is this “collapsed cursor” you speak of? And why do I care? :slight_smile:

I just mean the case where there isn't an extended selection, as in:

extended

collapsed

A simpler case than deciding whether we are moving the left end of the selection, or the right end, or both.

Oh, got it. And… what does the code you posted do that the other doesn’t?

If the other means my previous code rather than yours, the differences are that this one:

  1. Produces side effects (moves a cursor, rather than just mapping strings and indices to other indices)
  2. Parameterises the particular regexes, so that you can use others

Oh. Cool. If it moves a cursor, I didn’t notice, because I’m using the result to make a new selection.

Thanks!

This is all irrelevant if I can’t identify the text document currently being edited, when multiple documents are open. And currently, I can’t.

So don’t do any more work on this unless that gets solved. Personally, I give up. Like I said before, I was sure I had determined that I could get the “front text document” and that it was the document being edited, before I went down this path. I’m getting tired of saying this, but I was wrong.

Maybe I’m getting too old for this stuff, at 59. I’m making buffoon mistakes left and right. It doesn’t seem that long ago that I was the “go to” developer everywhere I worked. Sigh.

(Know the feeling – same age :slight_smile: )

PS looks like there was at least one bug report on this some years ago …

http://www.cocoabuilder.com/archive/xcode/306785-xcode4-applescript-how-to-get-the-current-text-document.html

PS if you want an editor that is deeply customisable, then https://atom.io/

Ah. Misery loves.. um, what were we talking about again? :slight_smile:

I saw that and I couldn't believe it was still true. Can't believe Apple, of all companies, would pay this little attention to automation in their own products. Except that I've already experienced this with Final Cut Pro X - however, if FCPX didn't have it's issues, I never would have gotten heavily into KM, so I'll count that one as a blessing.

I have Atom. I have the plugins for JavaScript. Can't for the life of me figure out how to compile, run, test, etc. Some of that has to do with my not understanding how Atom's key mapping works, and not caring enough to find out. However, if you told me I could use Atom legitimately to work on JXA, compile, etc, that would change my mind. Script Editor is about as low as you can go...

[begin rant]
One of the hard things about Apple development and other stuff, is that there isn't nearly as much information available as there is with Microsoft. You got a question about something on Windows, Visual Studio, something else? A hundred other people have posted the same question and generally gotten answers in duplicated Stack Overflow posts. And MS's documentation is pretty good, even if it suffers from too much, instead of not enough.

Because of that, I'm so used to learning by riding on the coattails of people who've gone before, I've forgotten how to be a developer in my own right. And when I don't have editor tools like ReSharper, I feel like I've gone back to using a manual typewriter. I have a friend who warned me about that. Should have listened.
[end rant]

While there is clearly not as much info about Apple/Mac stuff as with Microsoft, there still is quite a bit. The biggest challenge I have had is terminology. If you know the right terminology to search for, you can often find it. Sometimes it takes me multiple searches to first find the terminology, then search explicitly for it. (normally my google searches on other topics return the answer in the top 5 hits).

If you are referring to Mac automation, then you may want to spend some time at:
The Mac OS X Automation Sites, particularly at the MacOSXAutomation.com/AppleScript.

Unfortunately, there really isn't a good JXA equivalent. The best I know is:
JavaScript for Automation Cookbook -- a Github wiki site.

Even so, I have found questions about JavaScript and JXA to be answered at stackoverflow.com. There are even some tags for JXA:

Here's my JXA resource list:
JavaScript for Automation (JXA) Resources