Created
May 22, 2023 22:01
-
-
Save stephancasas/5e3f17978bff5e8387adf0731c749b48 to your computer and use it in GitHub Desktop.
AppleScript: Format selected text using unicode monospace glyph equivalents.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env osascript -l JavaScript | |
const $attr = Ref(); | |
const $windows = Ref(); | |
const $children = Ref(); | |
const UNICODE_PATTERN = '11110xxx10xxxxxx10xxxxxx10xxxxxx'; | |
function run(_) { | |
const app = $.AXUIElementCreateApplication( | |
$.NSWorkspace.sharedWorkspace.frontmostApplication.processIdentifier, | |
); | |
// Capture the focused UI element of the frontmost app. | |
$.AXUIElementCopyAttributeValue(app, 'AXFocusedUIElement', $attr); | |
const focusedElement = $attr[0].js; | |
// Does the captured UI element have a selected text attribute? | |
const $attributes = Ref(); | |
$.AXUIElementCopyAttributeNames(focusedElement, $attributes); | |
if (!$attributes[0].js.some((attr) => attr.js == 'AXSelectedText')) { | |
return; | |
} | |
// Capture the selected text string from the captured UI element. | |
$.AXUIElementCopyAttributeValue(focusedElement, 'AXSelectedText', $attr); | |
const selectedText = $attr[0].js; | |
// Exit if no content in captured string. | |
if (!selectedText) { | |
return; | |
} | |
// Convert captured string to unicode monospace character equivalents. | |
const monospaceText = selectedText | |
.split('') | |
.map((char) => { | |
if (!char.match(/(\d|[a-z])/i)) { | |
return char; | |
} | |
const offset = !!char.match(/\d/) | |
? 48 // numeric | |
: char.toLowerCase() == char | |
? 97 // lowercase | |
: 65; // uppercase | |
let unicodeDecimal = | |
char.charCodeAt(0) - | |
offset + | |
(120432 + (offset == 97 ? 26 : offset == 48 ? 390 : 0)); | |
let unicodeBinary = ''; | |
while (unicodeDecimal > 0) { | |
unicodeBinary = `${parseInt(unicodeDecimal) % 2}${unicodeBinary}`; | |
unicodeDecimal = parseInt(unicodeDecimal / 2); | |
} | |
let take = 0; | |
const utf8Binary = UNICODE_PATTERN.split('') | |
.reverse() | |
.map((seg) => { | |
if (seg == 'x') { | |
if (take >= unicodeBinary.length) { | |
return '0'; | |
} | |
take++; | |
return unicodeBinary[unicodeBinary.length - take]; | |
} | |
return seg; | |
}); | |
const utf8Decimal = utf8Binary | |
.reverse() | |
.join('') | |
.split('') | |
.reduce((acc, cur) => acc * 2 + parseInt(cur), 0); | |
const utf8Percent = utf8Decimal | |
.toString(16) | |
.split(/(..)/g) | |
.filter((byte) => !!byte) | |
.map((byte) => `%${byte}`) | |
.join(''); | |
return $.NSString.stringWithString(utf8Percent) | |
.stringByRemovingPercentEncoding.js; | |
}) | |
.join(''); | |
// Copy the monospaced text to the pasteboard. | |
$.NSPasteboard.generalPasteboard.clearContents; | |
$.NSPasteboard.generalPasteboard.writeObjects([monospaceText]); | |
// Capture the frontmost app's menu bar. | |
$.AXUIElementCopyAttributeValue(app, 'AXMenuBar', $attr); | |
// Locate the "Edit" menu bar item. | |
$.AXUIElementCopyAttributeValue($attr[0].js, 'AXChildren', $children); | |
const editMenuBarItem = $children[0].js.find((child) => { | |
$.AXUIElementCopyAttributeValue(child, 'AXTitle', $attr); | |
return `${$attr[0].js}`.toLowerCase() == 'edit'; | |
}); | |
// Get the children of the first menu of the "Edit" menu bar item. | |
$.AXUIElementCopyAttributeValue(editMenuBarItem, 'AXChildren', $children); | |
$.AXUIElementCopyAttributeValue($children[0].js[0], 'AXChildren', $children); | |
// Locate the "Paste" menu item by its identifier. | |
let pasteMenuItem = $children[0].js.find((child) => { | |
$.AXUIElementCopyAttributeValue(child, 'AXIdentifier', $attr); | |
return `${$attr[0].js}` == 'paste' || `${$attr[0].js}` == 'paste:'; | |
}); | |
// Activate the "Paste" menu item if discovered. | |
if (!!pasteMenuItem) { | |
$.AXUIElementPerformAction(pasteMenuItem, 'AXPress'); | |
return; | |
} | |
// Fallback to System Events dispatch of pasteboard accelerator. | |
Application('System Events').keystroke('v', { using: 'command down' }); | |
} | |
// prettier-ignore | |
(() => { | |
ObjC.import('Cocoa'); // yes, it's necessary -- stop telling me it isn't | |
ObjC.bindFunction('AXUIElementPerformAction', ['int', ['id', 'id']]); | |
ObjC.bindFunction('AXValueCreate', ['id', ['unsigned int', 'void*']]); | |
ObjC.bindFunction('AXUIElementCopyAttributeNames', ['int', ['id', 'id*']]); | |
ObjC.bindFunction('AXUIElementCreateApplication', ['id', ['unsigned int']]); | |
ObjC.bindFunction('AXUIElementSetAttributeValue', ['int', ['id', 'id', 'id']]); | |
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