Skip to content

Instantly share code, notes, and snippets.

@stephancasas
Created May 18, 2023 16:58
Show Gist options
  • Save stephancasas/0c9c749efff6bdacd4643a258d126076 to your computer and use it in GitHub Desktop.
Save stephancasas/0c9c749efff6bdacd4643a258d126076 to your computer and use it in GitHub Desktop.
Create New Untitled TextMate Document from AppleScript
#!/usr/bin/env osascript -l JavaScript
const $attr = Ref();
const $windows = Ref();
const $children = Ref();
function run(_) {
// Get TextMate AXUIElement
const textmatePid =
$.NSRunningApplication.runningApplicationsWithBundleIdentifier(
'com.macromates.TextMate',
).firstObject.processIdentifier;
const textmateApp = $.AXUIElementCreateApplication(textmatePid);
// Quit if TextMate is not running.
if (typeof textmatePid != 'number') {
console.log('Error: TextMate is not currently running.');
return 1;
}
// Get Dock AXUIElement
const dockPid =
$.NSRunningApplication.runningApplicationsWithBundleIdentifier(
'com.apple.dock',
).firstObject.processIdentifier;
const dockApp = $.AXUIElementCreateApplication(dockPid);
$.AXUIElementCopyAttributeValue(dockApp, 'AXChildren', $children);
// Locate the TextMate dock tile.
$.AXUIElementCopyAttributeValue($children[0].js[0], 'AXChildren', $children);
const textmateTile = $children[0].js.find((child) => {
$.AXUIElementCopyAttributeValue(child, 'AXTitle', $attr);
return $attr[0].js == 'TextMate';
});
// Open the TextMate dock tile menu -- delay for draw.
$.AXUIElementPerformAction(textmateTile, 'AXShowMenu');
delay(0.1);
// Capture the TextMate initial state for await reference and filtering.
$.AXUIElementCopyAttributeValue(textmateApp, 'AXWindows', $windows);
const initialWindowCount = $windows[0].js.length;
const initialWindowTitles = $windows[0].js.map((window) => {
$.AXUIElementCopyAttributeValue(window, 'AXTitle', $attr);
return $attr[0].js;
});
// Locate the "New File" menu item in the TextMate dock tile menu.
$.AXUIElementCopyAttributeValue(textmateTile, 'AXChildren', $children);
$.AXUIElementCopyAttributeValue($children[0].js[0], 'AXChildren', $children);
const newFileMenuItem = $children[0].js.find((child) => {
$.AXUIElementCopyAttributeValue(child, 'AXTitle', $attr);
return $attr[0].js == 'New File';
});
// Click the "New File" menu item and await new window.
$.AXUIElementPerformAction(newFileMenuItem, 'AXPress');
if (
!(() => {
const timeout = new Date().getTime() + 5000;
while (true) {
$.AXUIElementCopyAttributeValue(textmateApp, 'AXWindows', $windows);
if (
typeof $windows[0] == 'function' &&
($windows[0].js.length ?? 0) > initialWindowCount
) {
return true;
}
if (new Date().getTime() > timeout) {
return false;
}
delay(0.1);
}
})()
) {
console.log('Error: Did not observe new window before timeout.');
return 1;
}
// Find the window AXUIElement whose title is not in the initial list.
const newWindow = $windows[0].js.find((child) => {
$.AXUIElementCopyAttributeValue(child, 'AXTitle', $attr);
return !initialWindowTitles.includes($attr[0].js);
});
// Make the new window frontmost.
$.AXUIElementPerformAction(newWindow, 'AXRaise');
return 0;
}
// prettier-ignore
(() => {
ObjC.import('Cocoa'); // yes, it's necessary -- stop telling me it isn't
ObjC.bindFunction('AXUIElementPerformAction', ['int', ['id', 'id']]);
ObjC.bindFunction('AXUIElementCreateApplication', ['id', ['unsigned int']]);
ObjC.bindFunction('AXUIElementCopyAttributeValue',['int', ['id', 'id', 'id*']]);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment