Import and Use Numbered Code Snippets

I created a video course for iOS developers last year. While scripting the course, I created four code-snippets files. These files have between 50 and 150 snippets of Swift code. Here is an example of the first three snippets of one snippets file:

1.
import UIKit

class ViewController: UIViewController {}

2.
required init?(coder aDecoder: NSCoder) {
    NSCoder.fatalErrorNotImplemented()
}

3.
override func viewDidLoad() {
  breedCoordinator.start()
  settingsCoordinator.start()
  viewControllers = [breedCoordinator.navigationController, settingsCoordinator.navigationController]
}

While recording the course, I often said something like "Replace the function on line 42 with snippet 3." or "Create a file called ViewController.swift and replace its contents with snippet 1." In each case, I maximized the relevant snippets file, already open in Visual Studio Code, copied the relevant snippet, minus the identifying number, maximized Xcode, and pasted the snippet in Xcode. Xcode is the app which iOS developers use to create iOS apps.

This worked but was not ideal for the video-editing process. Assuming there were 400 snippets, I had to cut out of the edit 400 instances of maximizing the snippets file and copying a snippet. It would save much time to just type something like command-control-shift-1-enter for snippet 1 or command-control-shift-99-enter for snippet 99, avoiding the snippets file during the recording process.

I could make a shortcut for each individual snippet, but that would eliminate any potential time savings. I am looking for a way to bulk-import up to 150 snippets into Keyboard Maestro so that I can access them with something like the key combination described above. I would repeat the process for each of the four snippets files.

If you brought the whole collection in as a single JSON string (binding a Keyboard Maestro variable name to it),
then the %JSONValue% token ( see: token:JSONValue [Keyboard Maestro Wiki] ) would allow you to reference and extract particular snippets (by one-based index, for example, if the JSON is structured as an array).

1 Like

If you did want to go this route, rather than using a single macro with a JSON array (or KM text pseudo array), it wouldn't be that difficult to automate the creation of a Group of individual macros, one for each snippet with a typed string trigger corresponding to the snippet number, with AppleScript.

In this case I think I'd go with @ComplexPoint's suggestion. If you were going to be using these snippets a lot I'd make individual macros, my totally untested theory being that the individuals would be snappier since they wouldn't have to such a large variable to work with and you'd eventually regain the initial setup time and more.

I think I may be more optimistic than @Nige_S about performance –
even in the worst case, where the parsing of the whole snippet archive file is done in real time.

Here, to test and find out, is a subroutine:

  1. Install both the subroutine and the test macro which uses it, and
  2. paste the entire snippet archive into the variable local_Snippets, then
  3. specify the Index of a snippet to retrieve

Subroutine – Snippet by Index.kmmacros (4,5 Ko)

Test Snippet by Index.kmmacros (3.9 KB)


Expand disclosure triangle to view JS source
return (() => {
    "use strict";

    // Snippet by index from snippets text in format:
    // 1.
    // Codeline
    // Codeline
    // Codeline
    // ...
    // 2.
    // Codeline
    // Codeline
    // Codeline
    // etc

    const main = () => {
        const index = kmvar.local_Index;

        return isNaN(index)
            ? `Expected numeric index, saw: "${index}"`
            : unlines(
                snippetsTextAsDict(
                    kmvar.local_Snippets
                )[index] || [`Index "${index}" not found.`]
            );
    };


    // snippetsTextAsDict :: String -> {Index :: [String]}
    const snippetsTextAsDict = source => {
        const rgxNumberLine = /(^\d+)\./u;

        return lines(source).reduce(
            ([i, dict], s) => {
                const
                    m = s.match(rgxNumberLine),
                    isMatched = null !== m,
                    k = isMatched
                        ? m[1]
                        : "";


                return isMatched
                    ? newOrDuplicateIndex(k)(dict)
                    : [
                        i,
                        Object.assign(
                            dict,
                            {[i]: dict[i].concat(s)}
                        )
                    ];
            },
            [0, {}]
        )[1];
    };

    const newOrDuplicateIndex = k =>
        dict => [
            k,
            Object.assign(dict, {
                [k]: dict[k]
                    ? dict[k].concat(
                        `WARNING - SNIPPET INDEX (${k}.) REUSED:\n`
                    )
                    : []
            })
        ];


    // --------------------- GENERIC ---------------------

    // lines :: String -> [String]
    const lines = s =>
        // A list of strings derived from a single string
        // which is delimited by \n or by \r\n or \r.
        0 < s.length
            ? s.split(/\r\n|\n|\r/u)
            : [];


    // unlines :: [String] -> String
    const unlines = xs =>
        // A single string formed by the intercalation
        // of a list of strings with the newline character.
        xs.join("\n");

    // MAIN ---
    return main();
})();

You're probably right, especially on a "modern" machine. It's not the array access I'm worrying about, more the load time of some 10s of thousands of characters of text.

1 Like

And here we go...

Some assumptions have been made, such as "each snippet starts with one or more numbers followed by a period, at the beginning of a line -- and no other lines will start in that way". Most are obvious from the macro's actions.

The second action uses hard-coded text from the original post for testing -- that could easily be changed to read into the variable from a file.

Make sure that Local_theSep doesn't occur in the file -- change it so you don't get a bad split in the "For Each..." action. The macro group created/used will be called "Snippets" -- change lines 33 and 35 of the AppleScript if you want a different name. Macros will be called "Snippet n", where n is the snippet number from the original text (set on line 7). And the macros have a typed-string trigger of ";n;" (without the quotes), where n is again the snippet number (set on line 8).

This could certainly be improved -- execution efficiency was sacrificed for speed of development. And it's open to a wide range of tweaking to make it "yours".

One thing I haven't really tested is the behaviour with KM's "special" characters such as %. The "Insert by Pasting" action is set "Process Nothing" when it comes to tokens in an attempt to avoid any problems -- if you do want to process tokens in all the macros created, delete lines 15 and 16 from the AppleScript.

Snippet Maker.kmmacros (7.5 KB)

Image

1 Like

Updated the earlier post, above, to use the same triggering convention as @Nige_S's draft:

i.e. typing ;index; (where index is some integer) in a text editor.

(This version extracts the index from the %TriggerValue% token, and passes it to a subroutine, rather than requiring a separate macro for each snippet)

Seems instant, of course, for just 3 snippets – only experiment would reveal the extent to which it scales :slight_smile:

(though my guess is that is should be fast for several hundred snippets)

1 Like

One more step in converting everyone to "delimit your typed string triggers with ;"...

Today, @ComplexPoint. Tomorrow, the world! Bwa-ha-ha-ha...

1 Like

Updated again above to give a warning (in the output) if two or more snippets share the same numeric index.

Added another approach – drawing snippets from a JSON file, at:

@ComplexPoint's solution works, including with large snippet files. I will go with that in the course. That said, I intend to thank both ComplexPoint and @Nige_S in the credits screen of the next edition of iOSExpert. I hereby express my heartfelt thanks to both of you. This sort of JavaScript coding is way outside my iOS-development wheelhouse.

1 Like