Latest in Automation

Image credit:

Replicating QuickCursor using BBEdit and Keyboard Maestro

TJ Luoma, @tjluoma
August 30, 2013
Share
Tweet
Share

Sponsored Links

<p><a href="http://www.hogbaysoftware.com/products/quickcursor" target="_blank">QuickCursor</a> was the first app I bought on the Mac App Store. It was a great utility which let you send text from any application to your favorite text editor, and then when you were done, it would send the text back from your favorite text editor to the original application. For example, if I was writing a lengthy response to a question on <a href="http://apple.stackexchange.com" target="_blank">AskDifferent</a> in my web browser but didn't want to lose my work if the browser crashed, I could use QuickCursor to write it in BBEdit instead. (If you're still not clear on the concept, <a href="http://www.youtube.com/watch?v=-bHwcyHrRGs" target="_blank">watch the YouTube video for QuickCursor</a>.)</img></p><p>Unfortunately <a href="http://www.hogbaysoftware.com/products/quickcursor/faq" target="_blank">sandboxing killed QuickCursor</a>, meaning that it could no longer be sold through the Mac App Store. <a href="http://www.hogbaysoftware.com" target="_blank">Jesse Grosjean</a>, the developer, <a href="https://github.com/jessegrosjean/quickcursor" target="_blank">released the source code on GitHub</a> because he was not planning to continue updating it. (Can't say that I blame him after making a great-but-niche-market app for the Mac App Store, only to have Apple change the rules and make it impossible to continue selling it in the Mac App Store.)</p><p>I am still hopeful that someone will revive QuickCursor as a non-MAS app, but until that happens, I needed a temporary solution.</p><h3 id="bbeditandkeyboardmaestrototherescueagain.">BBEdit and Keyboard Maestro to the rescue, again.</h3><p>My solution to this problem is called <a href="https://github.com/tjluoma/edit-anywhere" target="_blank">Edit Anywhere</a>. It uses <a href="http://www.keyboardmaestro.com/main/" target="_blank">Keyboard Maestro</a> and <a href="http://barebones.com/products/bbedit/" target="_blank">BBEdit</a> to replicate <em>most</em> of the functionality from QuickCursor. (It would be possible to adapt this to other text editors as well. See the <a href="https://github.com/tjluoma/edit-anywhere/blob/master/README.md#there-are-a-few-provisos-a-couple-of-quid-pro-quos" target="_blank">GitHub README</a> for details.)</p><p>If you use those apps, all you have to do is <a href="https://raw.github.com/tjluoma/edit-anywhere/master/Edit-Anywhere.kmmacros" target="_blank">download and import the Keyboard Maestro macro</a> (note: make sure Keyboard Maestro is running before you try to import the .kmmacro file).</p><p><img align="right" alt="[BBEdit menu]" border="0" height="263" hspace="8" src="https://o.aolcdn.com/images/dar/5845cadfecd996e0372f/7a96a0258933ceea6309ddf990d97dd60a28d6e3/aHR0cDovL2ltYWdlcy5sdW8ubWEvZG9ub3Rtb3ZlL3R1YXdxdWlja2N1cnNvci9iYmVkaXQtaW5zdGFsbC1jb21tYW5kbGluZS10b29scy5qcGc=" vspace="8" width="291">Also, you'll need to make sure that you have installed BBEdit's command-line tool. If you purchased BBEdit directly from Bare Bones, you can use the "Install Command Line Tools..." menu option as show in the image here. (If you aren't sure if they are already installed, go ahead and use the menu. It will tell you if they are installed and up-to-date.) If you purchased BBEdit from the Mac App Store, you will need to <a href="http://www.barebones.com/support/bbedit/cmd-line-tools.html" target="_blank">download and install the tools from BareBones.com</a>.</img></p><h3 id="howitworksoverview">How it works (Overview)</h3><p>In Keyboard Maestro you will need to choose a keyboard shortcut to trigger 'Edit Anywhere.' I use Command+Option+Control+Shift+Q. (That might seems complicated, but I have remapped my <kbd>Caps Lock</kbd> key to equal Command+Option+Control+Shift, so all I have to press is <kbd>Caps Lock</kbd> + <kbd>Q</kbd>. See <a href="http://brettterpstra.com/2012/12/08/a-useful-caps-lock-key/" target="_blank">Brett Terpstra's "A Useful Caps Lock Key"</a> for more information.) You can, of course, set the keyboard shortcut to be anything you want.</p><p>The macro tries to determine if you have selected text in the current application. If yes, it will only use that text. Otherwise, it will select all of the text from the current application. That text will be 'cut', saved to a temporary file in your Home directory, and then opened in BBEdit. When you finish editing the file, simply close it and you will be taken back to the app that you were using, and the text will be pasted back into place.</p><p>If, for some reason, the process does not complete successfully, you will still have the edited text on your pasteboard and you can manually paste it wherever you want. Also, after each temporary 'Edit Anywhere' file is used it is moved to your Trash, where it will remain until you empty it, in case you need to recover text from one of those files.</p><h3 id="howitworksnerdydetaillevel">How it works (Nerdy Detail Level)</h3><p><em>If you don't care how this works 'under the hood' feel free to skip this section. I provided it for people who might be curious how to make their own Keyboard Maestro macros.</em></p><p>If you <a href="https://raw.github.com/tjluoma/edit-anywhere/master/Edit-Anywhere.kmmacros" target="_blank">download and import the macro into Keyboard Maestro</a>, you should be able to follow along as I explain each step.</p><p>First the macro checks to see if there is a menu item named 'Cut' which is enabled. <em>Most</em> Mac apps have an "Edit" menu with Cut, Copy, Paste, and Select All as sub-menu items. This is a quick test to see if the user has selected text in the current application. It is not foolproof, but it will cover us for <em>most</em> of the cases, and when it fails, all it means is that we will send <em>all</em> of the text to BBEdit instead of just the selected text.</p><p>If the 'Cut' menu is <em>not</em> enabled, then we can assume that need to do 'Select All' in order to capture all of the text from the current application. We do this first by trying the menu item "Edit -> Select All" and if that does not work, we fall back on <kbd>⌘</kbd> + <kbd>A</kbd> , the usual keyboard shortcut for 'Select All' in most apps.</p><p>(Again, this is not foolproof, but close enough for our purposes.)</p><p>Then I added a very short (0.2 seconds) pause to the Keyboard Maestro macro. When testing this macro, I found that some apps (notably Gmail compose 'windows' in web browsers) needed a little extra time to make sure that 'Select All' had selected all of the text. By trial and error I came up with 0.2 seconds which seemed reliable without adding too much of a delay.</p><p>Next we 'cut' the text using the menu "Edit -> Cut" if it exists or <kbd>⌘</kbd> + <kbd>X</kbd> (the common keyboard shortcut for 'Cut' in most apps). Why 'Cut' instead of 'Copy'? Wouldn't 'Copy' be safer since it leaves the text in the original app? There are a few reasons:</p><ol><li> <p>Sometimes you can 'copy' when you can't 'cut' or 'paste.' For example, you can select text on a web page, but you can't cut or paste back to it. Better to have the macro fail quickly than to offer to do something that won't work.</p> </li> <li> <p>If the selected text from the original application gets <em>unselected</em> for some reason (either because of some AJAX tomfoolery, or because the user pressed an arrow key or happened to click the mouse somewhere in the area), then when the replacement text comes back in, it might either a) intermix with the old text or b) be pasted in either before or after the old text.</p> </li> <li> <p>If the user happens to switch away from BBEdit while editing their text, and then went back to the source app and saw the original text, they might <em>forget</em> that it was opened in BBEdit and start making changes to the original text which is going to be overwritten when the revised text came back. It would be better for them to switch to the app and be surprised to see an empty window, which will hopefully remind them to check BBEdit.</p> </li> <li> <p>Although it might <em>seem</em> unsafe to 'cut' the text away, almost every single application will have an 'undo' option where they should be able to 'undo' the 'cut' and get their text back if something goes wrong with 'Edit Anywhere.'</p> </li></ol><p>Now, as soon as we have cut the text, we save it to the file <code>~/.edit_anywhere.txt</code> using <code>pbpaste</code> which is Apple's command line tool showing the contents of the pasteboard. Normally that file should not exist (we'll see why in a moment), but what if it does? Should we just overwrite it with the new content? Absolutely not, that might be leftover text that the user had forgotten was there. Instead, if the file exists, we'll append the new text to the file and then open it. That might give the user a moment of confusion, but it's easy enough to remove the text they do not want, certainly much easier than trying to recover overwritten information!</p><p>Once the information is safely stored in the file, we will try to open it with <code>/usr/local/bin/bbedit</code> which is BBEdit's command line tool.</p><p>(N.B. if you wanted to use a different tool other than BBEdit, this is the line you would want to change to something else such as <code>open -W -n -F *YourAppNameHere* "$FILE"</code> but then you would need to add a way to switch back to the proper application after you finished editing, and you would have to <strong>quit</strong> <em>YourAppNameHere</em> instead of just closing the window, which is all BBEdit requires. BBEdit's command-line tool is also smart enough to activate the proper app after it is finished. You can see why I chose BBEdit, especially since it is already my preferred text editor.)</p><p>What do we do if <code>bbedit</code> fails, or doesn't exist? The macro checks the exit code of <code>bbedit</code> and if it is not <code>0</code> then it checks to make sure <code>bbedit</code> is where it is expected. If it isn't, we inform the user. If it is, we inform the user that although <code>bbedit</code> is in the right place, it did not work properly. We also send the error message to stdout using <code>echo</code> which Keyboard Maestro will show the user because the shell script action is set to "display results in a window."</p><p>Then we open the ~/.edit_anywhere.txt file in the default text editor, using <code>open -W -t</code>. (See <code>man open</code> for more details.)</p><p>Assuming that <code>bbedit</code> does exist successfully, the shell exits and Keyboard Maestro will read the contents of the file back to the clipboard (we could have done that in the shell using <code>pbcopy < "$FILE"</code>). Then the macro will <code>paste</code> using the Edit -> Paste menu item, if available, or ⌘ + V.</p><p>Finally we run another shell script. This one looks for the <code>~/.edit_anywhere.txt</code> and moves it to the trash (~/.Trash/) but first it renames it using the current timestamp (YYYY-MM-DD–24h.MM.SS) and removes the leading '.' from the filename so it can be more easily seen if the user opens the Trash by clicking on the dock icon. This also prevents old versions from being overwritten in the Trash, in case the user needs to retrieve one for some reason.</p><h3 id="itsastart">It's a start</h3><p>'Edit Anywhere' isn't a perfect replacement for QuickCursor and, quite frankly, I'm still irritated that Apple failed to work harder to find ways to help developers make the transition into sandboxing. Too many good and useful apps have been either abandoned or forced out of the Mac App Store because of Apple's sandboxing implementation.</p><p>That said, 'Edit Anywhere' gives me most of what I needed as an alternative. I've only been using it for a short while, so there may be some weird bugs and edge cases out there, but I've tried to make it so that the worst thing that would happen is that you have to open your Trash and grab a text file. That said, I can't guarantee that you won't run into problems, so use as your own risk.</p> </div> </div> </div> </div> <div> <!-- article-boilerplate --> <!-- disclaimer --> <div class="o-article_block"> <div class="grid@tl+ mt-n40"> <div class="grid@tl+__cell col-8-of-12@tl+ pb-20@tp+ "> <div class="pb-35 border-top mt-20 mt-35@s pt-35 pt-30@m pt-25@s pb-25@s break-out@s"> <section class="t-meta c-gray-3"> <div class="mt-5">In this article: <span class="th-meta"> <a href="/tag/automation" class="th-meta">automation</a>, <a href="/tag/BBEdit" class="th-meta">BBEdit</a>, <a href="/tag/Keyboard Maestro" class="th-meta">Keyboard Maestro</a>, <a href="/tag/KeyboardMaestro" class="th-meta">KeyboardMaestro</a>, <a href="/tag/Mac" class="th-meta">Mac</a>, <a href="/tag/QuickCursor" class="th-meta">QuickCursor</a> </span> </div> </section> </div> <div class="t-meta c-gray-3 mb-35"> <em>All products recommended by Engadget are selected by our editorial team, independent of our parent company. Some of our stories include affiliate links. If you buy something through one of these links, we may earn an affiliate commission.</em> </div> <!-- inline-article-share-footer --> <div class="table pt-40@m+ pb-40@m+ pt-20@m- pb-20@m- border-top border-bottom ta-c@m-" id="engadget-article-footer" data-ylk="sec:articlefooter;itc:0;"> <div class="table-cell@m+ ta-r@m+ c-gray-9 hide@m-"> <div class="o-social_nav@m+"> <!-- share-macro --> <div class="o-social_nav__item" data-tooltip-outer> <a data-ylk="slk:facebook;elm:share;tar:https://facebook.com;" href="https://www.facebook.com/dialog/share?app_id=132746074315&display=popup&href=https%3A%2F%2Fwww.engadget.com%2F2013-08-30-replicating-quickcursor-using-bbedit-and-keyboard-maestro.html" class="inline-block pl-10@s th-meta sharebtn facebook-share" data-behavior="Tooltip Pop" target="_blank" data-engadget-Popname="engadget_share" data-engadget-pop-w="560" data-engadget-h="636" rel="noopener noreferrer"> <svg class="inline-block icon vm th-meta__icon"><use xlink:href="#icon-facebook"></use></svg> </a> <div class="hide" data-tooltip-content> <div class="o-tooltip ta-c th-reverse"> <div class="t-meta th-meta"> <span class="c-white">Share</span> <span data-share-count-facebook></span> </div> </div> </div> </div> <div class="o-social_nav__item" data-tooltip-outer> <a data-ylk="slk:twitter;elm:share;tar:https://twitter.com;" href="https://twitter.com/share?url=https%3A%2F%2Fwww.engadget.com%2F2013-08-30-replicating-quickcursor-using-bbedit-and-keyboard-maestro.html&text=Replicating%20QuickCursor%20using%20BBEdit%20and%20Keyboard%20Maestro&via=engadget" class="inline-block pl-10@s th-meta sharebtn twitter-share" data-behavior="Tooltip Pop" target="_blank" data-engadget-Popname="engadget_share" data-engadget-pop-w="670" data-engadget-h="430" rel="noopener noreferrer"> <svg class="inline-block icon vm th-meta__icon"><use xlink:href="#icon-twitter"></use></svg> </a> <div class="hide" data-tooltip-content> <div class="o-tooltip ta-c th-reverse"> <div class="t-meta th-meta"> <span class="c-white">Tweet</span> <span data-share-count-twitter></span> </div> </div> </div> </div> <div class="o-social_nav__item " data-tooltip-outer> <a data-ylk="slk:reddit;elm:share;tar:https://reddit.com;" href="https://www.reddit.com/submit?url=https%3A%2F%2Fwww.engadget.com%2F2013-08-30-replicating-quickcursor-using-bbedit-and-keyboard-maestro.html&title=Replicating%20QuickCursor%20using%20BBEdit%20and%20Keyboard%20Maestro" class="inline-block pl-10@s th-meta sharebtn reddit-share" data-behavior="Tooltip Pop" target="_blank" data-engadget-Popname="engadget_share" data-engadget-pop-w="960" data-engadget-h="750" rel="noopener noreferrer"> <svg class="inline-block icon vm th-meta__icon"><use xlink:href="#icon-reddit"></use></svg> </a> <div class="hide" data-tooltip-content> <div class="o-tooltip ta-c th-reverse"> <div class="t-meta th-meta"> <span class="c-white">Share</span> <span data-share-count-reddit></span> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </article> </div> </div> </div> <!-- article-recirculation-module --> <section class="container mb-20 mt-n40@tp+ " id="engadget-recirculation-module" data-ylk="sec:recirc;subsec:more-from;itc:0;"> <!-- section-header --> <header class=" relative z-1 bc-gray-1"> <div class="container"> <div class="table"> <div class="o-title_mark table-cell-bottom"> <h2 class="th-title t-section-title mt-15 pb-0"> Popular on Engadget </h2> </div> </div> </div> </header> <div class="pt-20"> <div class="grid flex flex-wrap mt-n60@tp+ o-grid_divider_mask"> <div class="hide@m- o-grid_divider_mask__mask bg-white"></div> <div class="full-width"> <!-- recirc-module-card --> <div class="grid__cell mt-60@tp+ col-1-of-3@tp col-1-of-5@tl+ grid-divider@tp+ border-bottom@m- pb-20@m- pt-20@m- "> <div class="o-hit"> <div class="col-2-of-7@m- right"> <div class="contain mb-15@tp+"> <img src="https://o.aolcdn.com/images/dims?thumbnail=215%2C215&quality=95&image_uri=https%3A%2F%2Fs.yimg.com%2Fos%2Fcreatr-uploaded-images%2F2021-01%2F892b7360-5ee5-11eb-afe7-20c0360545f3&client=amp-blogside-v2&signature=2ac549ba6d4f7640988c094b7f184dcdc9a5ff76" class="stretch-img" alt="A personal trainer app guilt-tripped me into exercising (and it worked)" style="border-radius: 4px;"> </div> </div> <div class="col-5-of-7@m- pr-40@m-"> <h3 class="t-h5-b@m- t-bold@m- t-h5@tp t-h6@tl t-h5@d mt-5@tp+ c-black"> <span class="th-underline"> A personal trainer app guilt-tripped me into exercising (and it worked) </span> </h3> </div> <a data-ylk="slk:A%20personal%20trainer%20app%20guilt-tripped%20me%20into%20exercising%20%28and%20it%20worked%29;elm:hdln;" href="https://www.engadget.com/future-fit-personal-trainer-app-163040652.html?itm_source=parsely-api" class="o-hit__link">View</a> </div> </div> <!-- recirc-module-card --> <div class="grid__cell mt-60@tp+ col-1-of-3@tp col-1-of-5@tl+ grid-divider@tp+ border-bottom@m- pb-20@m- pt-20@m- "> <div class="o-hit"> <div class="col-2-of-7@m- right"> <div class="contain mb-15@tp+"> <img src="https://o.aolcdn.com/images/dims?thumbnail=215%2C215&quality=95&image_uri=https%3A%2F%2Fs.yimg.com%2Fos%2Fcreatr-uploaded-images%2F2021-01%2F84b53ea0-5f13-11eb-afbe-0f40f9d1bd7f&client=amp-blogside-v2&signature=f4e3bcf3491d5137edecb810126d85ea6da9e886" class="stretch-img" alt="Scientists find a cloudless 'hot Jupiter' exoplanet with a four-day year" style="border-radius: 4px;"> </div> </div> <div class="col-5-of-7@m- pr-40@m-"> <h3 class="t-h5-b@m- t-bold@m- t-h5@tp t-h6@tl t-h5@d mt-5@tp+ c-black"> <span class="th-underline"> Scientists find a cloudless 'hot Jupiter' exoplanet with a four-day year </span> </h3> </div> <a data-ylk="slk:Scientists%20find%20a%20cloudless%20%27hot%20Jupiter%27%20exoplanet%20with%20a%20four-day%20year;elm:hdln;" href="https://www.engadget.com/cloudless-hot-jupiter-exoplanet-144728972.html?itm_source=parsely-api" class="o-hit__link">View</a> </div> </div> <!-- recirc-module-card --> <div class="grid__cell mt-60@tp+ col-1-of-3@tp col-1-of-5@tl+ grid-divider@tp+ border-bottom@m- pb-20@m- pt-20@m- "> <div class="o-hit"> <div class="col-2-of-7@m- right"> <div class="contain mb-15@tp+"> <img src="https://o.aolcdn.com/images/dims?thumbnail=215%2C215&quality=95&image_uri=https%3A%2F%2Fs.yimg.com%2Fos%2Fcreatr-uploaded-images%2F2021-01%2Fa6cb2e50-5f09-11eb-bfef-1275621f5a30&client=amp-blogside-v2&signature=a0ff3d6a1638db69fc3385a2f17e447ac128da4a" class="stretch-img" alt="Huawei may spin off its P and Mate smartphone brands" style="border-radius: 4px;"> </div> </div> <div class="col-5-of-7@m- pr-40@m-"> <h3 class="t-h5-b@m- t-bold@m- t-h5@tp t-h6@tl t-h5@d mt-5@tp+ c-black"> <span class="th-underline"> Huawei may spin off its P and Mate smartphone brands </span> </h3> </div> <a data-ylk="slk:Huawei%20may%20spin%20off%20its%20P%20and%20Mate%20smartphone%20brands;elm:hdln;" href="https://www.engadget.com/huawei-spin-off-p-and-mate-smartphone-brands-124438927.html?itm_source=parsely-api" class="o-hit__link">View</a> </div> </div> <!-- recirc-module-card --> <div class="grid__cell mt-60@tp+ col-1-of-3@tp col-1-of-5@tl+ grid-divider@tp+ border-bottom@m- pb-20@m- pt-20@m- hide@tp"> <div class="o-hit"> <div class="col-2-of-7@m- right"> <div class="contain mb-15@tp+"> <img src="https://o.aolcdn.com/images/dims?thumbnail=215%2C215&quality=95&image_uri=https%3A%2F%2Fs.yimg.com%2Fos%2Fcreatr-uploaded-images%2F2021-01%2F2ec5d620-5f28-11eb-9efe-94b9b79597a8&client=amp-blogside-v2&signature=908f35209461540cd7f2455aa7e23abb53e046ec" class="stretch-img" alt="AMC avoids bankruptcy, at least for now" style="border-radius: 4px;"> </div> </div> <div class="col-5-of-7@m- pr-40@m-"> <h3 class="t-h5-b@m- t-bold@m- t-h5@tp t-h6@tl t-h5@d mt-5@tp+ c-black"> <span class="th-underline"> AMC avoids bankruptcy, at least for now </span> </h3> </div> <a data-ylk="slk:AMC%20avoids%20bankruptcy%2C%20at%20least%20for%20now;elm:hdln;" href="https://www.engadget.com/amc-avoids-bankruptcy-with-funding-164243952.html?itm_source=parsely-api" class="o-hit__link">View</a> </div> </div> <!-- recirc-module-card --> <div class="grid__cell mt-60@tp+ col-1-of-3@tp col-1-of-5@tl+ grid-divider@tp+ border-bottom@m- pb-20@m- pt-20@m- hide@tp"> <div class="o-hit"> <div class="col-2-of-7@m- right"> <div class="contain mb-15@tp+"> <img src="https://o.aolcdn.com/images/dims?thumbnail=215%2C215&quality=95&image_uri=https%3A%2F%2Fs.yimg.com%2Fos%2Fcreatr-uploaded-images%2F2021-01%2F2761e400-5f19-11eb-acdf-bdb42a01fc2e&client=amp-blogside-v2&signature=f57d98eaa6b8a65a56a3f3042c4f322ec7e4fdeb" class="stretch-img" alt="Apple's 'Time to Walk' stories arrive today for Fitness+ users" style="border-radius: 4px;"> </div> </div> <div class="col-5-of-7@m- pr-40@m-"> <h3 class="t-h5-b@m- t-bold@m- t-h5@tp t-h6@tl t-h5@d mt-5@tp+ c-black"> <span class="th-underline"> Apple's 'Time to Walk' stories arrive today for Fitness+ users </span> </h3> </div> <a data-ylk="slk:Apple%27s%20%27Time%20to%20Walk%27%20stories%20arrive%20today%20for%20Fitness%2B%20users;elm:hdln;" href="https://www.engadget.com/apple-time-to-walk-audio-stories-fitness-plus-142555084.html?itm_source=parsely-api" class="o-hit__link">View</a> </div> </div> </div> </div> </div> </section> <div class="container"> <div class="grid"> <div class="grid__cell col-12-of-15@d push-3-of-15@d"> <div class="col-8-of-12@tl+"> </div> </div> </div> </div> <!-- section-title --> <div class="o-title_mark pb-15@ mt-20 mb-20 container bc-gray-2"> <h2 class="t-section-title th-title mt-15"> From around the web </h2> </div> <div class="container"> <div class="grid"> <div class="col-12-of-12 grid__cell"> <div id="taboola-below-article-thumbnails"></div> <script type="text/javascript"> window._taboola = window._taboola || []; _taboola.push({ mode: 'thumbnails-b', container: 'taboola-below-article-thumbnails', placement: 'Below Article Thumbnails', target_type: 'mix' }); </script> </div> </div> </div> <div class="ta-c mt-40"> <!-- inline-leaderboard-ad --> <aside role="banner" class="inlineLb"> <div class="vc relative full-width mb-25"><div id="leaderboard_inline_article_ncp_redirector" class="" data-aol-adcall="{"tp":{"adSetType":"F","adSetInView":1,"htmlAdWH":{"mn":963875579,"sizes":{"w":"LB","h":"LB"},"type":"f"},"adsRotateMult":1}}" data-behavior="Advertisement"></div></div> </aside> </div> <div class="ta-c mt-20@s mb-20@s"> <aside role="banner"> <div class="vc relative stretch-img"> <div class="ml-a mr-a" style="width: 300px;"><div id="mo_300x250_article_ncp_redirector" class="" data-aol-adcall="{"s":{"adSetType":"F","adSetInView":1,"htmlAdWH":{"mn":963875580,"sizes":{"w":"MM","h":"MM"},"type":"f"},"adsRotateMult":1},"m":false}" data-behavior="Advertisement"></div></div> </div> </aside> </div> <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Article", "url": "https://www.engadget.com/2013-08-30-replicating-quickcursor-using-bbedit-and-keyboard-maestro.html", "author": [ { "@type": "Person", "url": "https://www.engadget.com/about/editors/", "name": "TJ Luoma" } ], "headline": "Replicating QuickCursor using BBEdit and Keyboard Maestro", "datePublished": "Fri, Aug 30 2013 09:00:00 EDT", "mainEntityOfPage": "True", "thumbnailUrl": "https://s.yimg.com/uu/api/res/1.2/Hm8C9thXr4aSQwRXwT8cbA--~B/aD0yNTA7dz0yNTA7YXBwaWQ9eXRhY2h5b24-/http://images.luo.ma/donotmove/tuawquickcursor/keyboard-maestro-app-icon-250x250-1377815171.jpg", "image": [ { "@type": "ImageObject", "url": "https://o.aolcdn.com/images/dims?thumbnail=640%2C480&quality=95&image_uri=https%3A%2F%2Fs.yimg.com%2Fuu%2Fapi%2Fres%2F1.2%2FHm8C9thXr4aSQwRXwT8cbA--%7EB%2FaD0yNTA7dz0yNTA7YXBwaWQ9eXRhY2h5b24-%2Fhttp%3A%2F%2Fimages.luo.ma%2Fdonotmove%2Ftuawquickcursor%2Fkeyboard-maestro-app-icon-250x250-1377815171.jpg&client=amp-blogside-v2&signature=86eb31027cb03c9a90dcc8d3efd9d867ccf8c1ab", "width": "640px", "height": "480px" } ], "articleBody": "QuickCursor was the first app I bought on the Mac App Store. It was a great utility which let you send text from any application to your favorite text editor, and then when you were done, it would send the text back from your favorite text editor to the original application. For example, if I was writing a lengthy response to a question on AskDifferent in my web browser but didn't want to lose my work if the browser crashed, I could use QuickCursor to write it in BBEdit instead. (If you're still not clear on the concept, watch the YouTube video for QuickCursor.)\n\nUnfortunately sandboxing killed QuickCursor, meaning that it could no longer be sold through the Mac App Store. Jesse Grosjean, the developer, released the source code on GitHub because he was not planning to continue updating it. (Can't say that I blame him after making a great-but-niche-market app for the Mac App Store, only to have Apple change the rules and make it impossible to continue selling it in the Mac App Store.)\n\nI am still hopeful that someone will revive QuickCursor as a non-MAS app, but until that happens, I needed a temporary solution.\n\nBBEdit and Keyboard Maestro to the rescue, again.\n\nMy solution to this problem is called Edit Anywhere. It uses Keyboard Maestro and BBEdit to replicate most of the functionality from QuickCursor. (It would be possible to adapt this to other text editors as well. See the GitHub README for details.)\n\nIf you use those apps, all you have to do is download and import the Keyboard Maestro macro (note: make sure Keyboard Maestro is running before you try to import the .kmmacro file).\n\n\nAlso, you'll need to make sure that you have installed BBEdit's command-line tool. If you purchased BBEdit directly from Bare Bones, you can use the "Install Command Line Tools..." menu option as show in the image here. (If you aren't sure if they are already installed, go ahead and use the menu. It will tell you if they are installed and up-to-date.) If you purchased BBEdit from the Mac App Store, you will need to download and install the tools from BareBones.com.\n\nHow it works (Overview)\n\nIn Keyboard Maestro you will need to choose a keyboard shortcut to trigger 'Edit Anywhere.' I use Command+Option+Control+Shift+Q. (That might seems complicated, but I have remapped my Caps Lock key to equal Command+Option+Control+Shift, so all I have to press is Caps Lock + Q. See Brett Terpstra's "A Useful Caps Lock Key" for more information.) You can, of course, set the keyboard shortcut to be anything you want.\n\nThe macro tries to determine if you have selected text in the current application. If yes, it will only use that text. Otherwise, it will select all of the text from the current application. That text will be 'cut', saved to a temporary file in your Home directory, and then opened in BBEdit. When you finish editing the file, simply close it and you will be taken back to the app that you were using, and the text will be pasted back into place.\n\nIf, for some reason, the process does not complete successfully, you will still have the edited text on your pasteboard and you can manually paste it wherever you want. Also, after each temporary 'Edit Anywhere' file is used it is moved to your Trash, where it will remain until you empty it, in case you need to recover text from one of those files.\n\nHow it works (Nerdy Detail Level)\n\nIf you don't care how this works 'under the hood' feel free to skip this section. I provided it for people who might be curious how to make their own Keyboard Maestro macros.\n\nIf you download and import the macro into Keyboard Maestro, you should be able to follow along as I explain each step.\n\nFirst the macro checks to see if there is a menu item named 'Cut' which is enabled. Most Mac apps have an "Edit" menu with Cut, Copy, Paste, and Select All as sub-menu items. This is a quick test to see if the user has selected text in the current application. It is not foolproof, but it will cover us for most of the cases, and when it fails, all it means is that we will send all of the text to BBEdit instead of just the selected text.\n\nIf the 'Cut' menu is not enabled, then we can assume that need to do 'Select All' in order to capture all of the text from the current application. We do this first by trying the menu item "Edit -> Select All" and if that does not work, we fall back on ⌘ + A , the usual keyboard shortcut for 'Select All' in most apps.\n\n(Again, this is not foolproof, but close enough for our purposes.)\n\nThen I added a very short (0.2 seconds) pause to the Keyboard Maestro macro. When testing this macro, I found that some apps (notably Gmail compose 'windows' in web browsers) needed a little extra time to make sure that 'Select All' had selected all of the text. By trial and error I came up with 0.2 seconds which seemed reliable without adding too much of a delay.\n\nNext we 'cut' the text using the menu "Edit -> Cut" if it exists or ⌘ + X (the common keyboard shortcut for 'Cut' in most apps). Why 'Cut' instead of 'Copy'? Wouldn't 'Copy' be safer since it leaves the text in the original app? There are a few reasons:\n\n\n\n\tSometimes you can 'copy' when you can't 'cut' or 'paste.' For example, you can select text on a web page, but you can't cut or paste back to it. Better to have the macro fail quickly than to offer to do something that won't work.\n\t\n\t\n\tIf the selected text from the original application gets unselected for some reason (either because of some AJAX tomfoolery, or because the user pressed an arrow key or happened to click the mouse somewhere in the area), then when the replacement text comes back in, it might either a) intermix with the old text or b) be pasted in either before or after the old text.\n\t\n\t\n\tIf the user happens to switch away from BBEdit while editing their text, and then went back to the source app and saw the original text, they might forget that it was opened in BBEdit and start making changes to the original text which is going to be overwritten when the revised text came back. It would be better for them to switch to the app and be surprised to see an empty window, which will hopefully remind them to check BBEdit.\n\t\n\t\n\tAlthough it might seem unsafe to 'cut' the text away, almost every single application will have an 'undo' option where they should be able to 'undo' the 'cut' and get their text back if something goes wrong with 'Edit Anywhere.'\n\t\n\nNow, as soon as we have cut the text, we save it to the file ~\/.edit_anywhere.txt using pbpaste which is Apple's command line tool showing the contents of the pasteboard. Normally that file should not exist (we'll see why in a moment), but what if it does? Should we just overwrite it with the new content? Absolutely not, that might be leftover text that the user had forgotten was there. Instead, if the file exists, we'll append the new text to the file and then open it. That might give the user a moment of confusion, but it's easy enough to remove the text they do not want, certainly much easier than trying to recover overwritten information!\n\nOnce the information is safely stored in the file, we will try to open it with \/usr\/local\/bin\/bbedit which is BBEdit's command line tool.\n\n(N.B. if you wanted to use a different tool other than BBEdit, this is the line you would want to change to something else such as open -W -n -F *YourAppNameHere* "$FILE" but then you would need to add a way to switch back to the proper application after you finished editing, and you would have to quit YourAppNameHere instead of just closing the window, which is all BBEdit requires. BBEdit's command-line tool is also smart enough to activate the proper app after it is finished. You can see why I chose BBEdit, especially since it is already my preferred text editor.)\n\nWhat do we do if bbedit fails, or doesn't exist? The macro checks the exit code of bbedit and if it is not 0 then it checks to make sure bbedit is where it is expected. If it isn't, we inform the user. If it is, we inform the user that although bbedit is in the right place, it did not work properly. We also send the error message to stdout using echo which Keyboard Maestro will show the user because the shell script action is set to "display results in a window."\n\nThen we open the ~\/.edit_anywhere.txt file in the default text editor, using open -W -t. (See man open for more details.)\n\nAssuming that bbedit does exist successfully, the shell exits and Keyboard Maestro will read the contents of the file back to the clipboard (we could have done that in the shell using pbcopy < "$FILE"). Then the macro will paste using the Edit -> Paste menu item, if available, or ⌘ + V.\n\nFinally we run another shell script. This one looks for the ~\/.edit_anywhere.txt and moves it to the trash (~\/.Trash\/) but first it renames it using the current timestamp (YYYY-MM-DD–24h.MM.SS) and removes the leading '.' from the filename so it can be more easily seen if the user opens the Trash by clicking on the dock icon. This also prevents old versions from being overwritten in the Trash, in case the user needs to retrieve one for some reason.\n\nIt's a start\n\n'Edit Anywhere' isn't a perfect replacement for QuickCursor and, quite frankly, I'm still irritated that Apple failed to work harder to find ways to help developers make the transition into sandboxing. Too many good and useful apps have been either abandoned or forced out of the Mac App Store because of Apple's sandboxing implementation.\n\nThat said, 'Edit Anywhere' gives me most of what I needed as an alternative. I've only been using it for a short while, so there may be some weird bugs and edge cases out there, but I've tried to make it so that the worst thing that would happen is that you have to open your Trash and grab a text file. That said, I can't guarantee that you won't run into problems, so use as your own risk.", "articleSection": "Uncategorized", "keywords": ["automation","BBEdit","Keyboard Maestro","KeyboardMaestro","Mac","QuickCursor"], "publisher": [ { "@type": "Organization", "name": "Engadget", "url": "https://www.engadget.com", "logo": [ { "@type": "ImageObject", "url": "https://www.engadget.com/assets/images/eng-e-128.png", "width": "128px", "height": "128px" } ] } ], "dateModified": "Thu, Feb 6 2020 18:04:47 EST" } </script> </main> <!-- global-footer --> <footer role="navigation" id="engadget-global-footer" data-ylk-"sec:footer;itc:0;"> <nav class="th-reverse pt-40 pb-60 h-450 hide@tp-"> <div class="container full-height"> <div class="grid full-height"> <div class="grid__cell col-1-of-4 full-height"> <section> <!-- list-header --> <div class=" bc-violet o-title_mark"> <h3 class=" th-title t-list-header mt-10"> About </h3> </div> <ul class="t-meta-list mt-20 c-white-50"> <li><a data-ylk="slk:About%20Engadget;elm:category;itc:0;" href="https://www.engadget.com/about/" class="c-white:hvr">About Engadget</a></li> <li><a data-ylk="slk:About%20Our%20Ads;elm:category;itc:0;" href="https://www.engadget.com/about-our-advertising/" class="c-white:hvr">About Our Ads</a></li> <li><a data-ylk="slk:Advertise;elm:category;itc:0;" href="https://www.engadget.com/about/advertise/" class="c-white:hvr">Advertise</a></li> <li><a data-ylk="slk:Brand%20Kit;elm:category;itc:0;" href="https://o.aolcdn.com/engadget/brand-kit/eng-logo-guidelines.pdf" class="c-white:hvr">Brand Kit</a></li> <li><a data-ylk="slk:Buyers%20Guide;elm:category;itc:0;" href="https://www.engadget.com/about/buyers-guide/" class="c-white:hvr">Buyers Guide</a></li> <li><a data-ylk="slk:RSS%20Feed;elm:category;itc:0;" href="https://www.engadget.com/rss.xml" class="c-white:hvr">RSS Feed</a></li> </ul> </section> </div> <div class="grid__cell col-1-of-4 grid-divider bc-white-10 full-height"> <section> <!-- list-header --> <div class=" bc-violet o-title_mark"> <h3 class=" th-title t-list-header mt-10"> Sections </h3> </div> <ul class="t-meta-list mt-20 c-white-50"> <li><a data-ylk="slk:Reviews;elm:category;itc:0;" href="https://www.engadget.com/reviews/" class="c-white:hvr">Reviews</a></li> <li><a data-ylk="slk:Gear;elm:category;itc:0;" href="https://www.engadget.com/gear/" class="c-white:hvr">Gear</a></li> <li><a data-ylk="slk:Gaming;elm:category;itc:0;" href="https://www.engadget.com/gaming/" class="c-white:hvr">Gaming</a></li> <li><a data-ylk="slk:Entertainment;elm:category;itc:0;" href="https://www.engadget.com/entertainment/" class="c-white:hvr">Entertainment</a></li> <li><a data-ylk="slk:Tomorrow;elm:category;itc:0;" href="https://www.engadget.com/tomorrow/" class="c-white:hvr">Tomorrow</a></li> <li><a data-ylk="slk:The%20Buyer%27s%20Guide;elm:category;itc:0;" href="https://www.engadget.com/buyers-guide/" class="c-white:hvr">The Buyer's Guide</a></li> <li><a data-ylk="slk:Video;elm:category;itc:0;" href="https://www.engadget.com/videos/" class="c-white:hvr">Video</a></li> <li><a data-ylk="slk:Podcasts;elm:category;itc:0;" href="https://www.engadget.com/podcasts/" class="c-white:hvr">Podcasts</a></li> <li><a data-ylk="slk:Deals;elm:category;itc:0;" href="http://deals.gdgt.com/" class="c-white:hvr">Deals</a></li> </ul> </section> </div> <div class="grid__cell col-1-of-4 grid-divider bc-white-10 full-height"> <section> <!-- list-header --> <div class=" bc-violet o-title_mark"> <h3 class=" th-title t-list-header mt-10"> Contribute </h3> </div> <ul class="t-meta-list mt-20 c-white-50"> <li><a data-ylk="slk:Comment%20Guidelines;elm:category;itc:0;" href="https://www.engadget.com/2017/05/01/engadget-commenting-policy/" class="c-white:hvr">Comment Guidelines</a></li> <li><a data-ylk="slk:Send%20us%20a%20tip%21;elm:category;itc:0;" href="https://www.engadget.com/about/tips/" class="c-white:hvr">Send us a tip!</a></li> <li><a data-ylk="slk:Support;elm:category;itc:0;" href="https://www.engadget.com/about/support/" class="c-white:hvr">Support</a></li> </ul> </section> </div> <div class="grid__cell col-1-of-4 grid-divider bc-white-10 full-height"> <section> <!-- list-header --> <div class=" bc-violet o-title_mark"> <h3 class=" th-title t-list-header mt-10"> International </h3> </div> <ul class="t-meta-list mt-20 c-white-50"> <li><a data-ylk="slk:%E7%B9%81%E9%AB%94%E4%B8%AD%E6%96%87;elm:category;itc:0;" href="http://chinese.engadget.com/" class="c-white:hvr">繁體中文</a></li> <li><a data-ylk="slk:%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87;elm:category;itc:0;" href="http://cn.engadget.com/" class="c-white:hvr">简体中文</a></li> <li><a data-ylk="slk:%E6%97%A5%E6%9C%AC%E7%89%88;elm:category;itc:0;" href="http://japanese.engadget.com/" class="c-white:hvr">日本版</a></li> </ul> </section> </div> </div> </div> </nav> <nav class="pb-25@tp- th-reverse bg-gray-2@tl+"> <div class="container"> <div class="grid"> <section class="grid__cell col-2-of-5 col-12-of-12@tp- pt-40 pt-30@tp- hide@tp-"> <div class="break-out@tp- border-bottom pb-40 pb-30@tp-"> <div class="container@tp-"> <span class="hide@tl+ t-list-header-2@tp- c-white@tp- mb-20@tp-">Join Our Newsletter</span> <form class="relative pr-190@tl+" action="/a/mail_subscribe" method="post"> <input type="hidden" value="eyJyIjoiTVNyV015bGVCcXEzZ2wrRTQzdENrdmdXSkh2K1RjUmlXMXBFVGRoR3dJbz0iLCJ0IjoxNjExNTk5ODQ0LCJzIjoiUWNLUzlESVwvakh5RHJscnhTNStwdnB1MEJYbklQTWs5YUs5XC81c3RGNUQ0SVRKUzVIYnNcL1plYWhoRWxUYkVPUjdVTFgwem9VZjFuZFc1cU9rcFdKemc9PSJ9" name="csrf"> <input type="text" name="email" placeholder="Sign up for our newsletter" class="input pl-15 pr-15 rounded c-gray-1 bg-white-90 tx-meta pr-100@tp-"/> <input type="hidden" name="redirect" value="https://www.engadget.com/thanks"/> <div class="absolute@tl+ t-0 r-0 w-170@tl+"> <button type="submit" class="o-btn@tl+ o-btn@tl+--small th-btn@tl+ absolute@tp- c-violet@tp- t-0 b-0 r-0 pl-20@tp- pr-20@tp- bc-white-50" data-ylk="pos:1;cpos:1;elm:subscribe;"> <span class="t-btn t-meta@s">Subscribe</span> </button> </div> </form> </div> </div> </section> <section class="grid__cell col-3-of-5 col-12-of-12@tp- pt-60 pt-20@tp- bc-white-10 ta-r@tl+" id="engadget-followus" data-ylk="sec:followus;itc:0;"> <div class="container@tp-"> <span class="t-list-header-2@tp- c-white@tp- vc t-meta@tl+ c-gray-7@tl+">Follow Us</span> <ul class="vc c-gray-6 fixed-table@tp- mt-20@tp-"> <li class="inline-block@tl+ table-cell@tp- ml-30@tl+"> <a data-ylk="pos:1;slk:facebook;elm:follow;itc:0;" href="https://www.facebook.com/Engadget" class="c-white:hvr"> <svg class="w-15@s h-15@s h-22@m+ w-22@m+ inline-block"> <use xlink:href="#icon-facebook"></use> </svg> </a> </li> <li class="inline-block@tl+ table-cell@tp- ml-40@tl+"> <a data-ylk="pos:2;slk:twitter;elm:follow;itc:0;" href="https://twitter.com/engadget" class="c-white:hvr"> <svg class="w-15@s h-15@s h-22@m+ w-22@m+ inline-block"> <use xlink:href="#icon-twitter"></use> </svg> </a> </li> <li class="inline-block@tl+ table-cell@tp- ml-40@tl+"> <a data-ylk="pos:4;slk:youtube;elm:follow;itc:0;" href="https://www.youtube.com/engadget" class="c-white:hvr"> <svg class="w-15@s h-15@s h-22@m+ w-22@m+ inline-block"> <use xlink:href="#icon-youtube"></use> </svg> </a> </li> <li class="inline-block@tl+ table-cell@tp- ml-40@tl+"> <a data-ylk="pos:5;slk:instagram;elm:follow;itc:0;" href="https://instagram.com/engadget" class="c-white:hvr"> <svg class="w-15@s h-15@s h-22@m+ w-22@m+ inline-block"> <use xlink:href="#icon-instagram"></use> </svg> </a> </li> <li class="inline-block@tl+ table-cell@tp- ml-40@tl+"> <a data-ylk="pos:7;slk:linkedin;elm:follow;itc:0;" href="https://www.linkedin.com/company/101421" class="c-white:hvr"> <svg class="w-15@s h-15@s h-22@m+ w-22@m+ inline-block"> <use xlink:href="#icon-linkedin"></use> </svg> </a> </li> </ul> </div> </section> </div> </div> </nav> <div class="c-gray-6 pt-25@tp- pb-20@tp-" id="engadget-footer-links" data-ylk="sec:footerlinks;"> <div class="container ta-l"> <div class="table@tl+ h-80@tl+ mb-20@tp-"> <div class="table-cell@tl+"> <div class="t-meta">© 2021 Verizon Media. All rights reserved.</div> </div> </div> <div class="table h-40 mb-25"> <div class="table-cell@tl+"> <ul class="ml-n20 ml-n10@tp-"> <li class="pl-20 pl-10@tp- inline-block"> <a data-ylk="elm:category;itc:0;" href="https://policies.oath.com/us/en/oath/privacy/guce/faq/index.html" target="_blank" class="t-meta-small th-meta-small" rel="noopener noreferrer">About Verizon Media</a> </li> <li class="pl-20 pl-10@tp- inline-block"> <a data-ylk="elm:category;etc:0;" href="https://www.parsintl.com/publication/engadget/" target="_blank" class="t-meta-small th-meta-small" rel="noopener noreferrer">Reprints and Permissions</a> </li> <li class="pl-20 pl-10@tp- inline-block"> <a data-ylk="elm:category;itc:0;" href="https://aol.uservoice.com/forums/917323" target="_blank" class="t-meta-small th-meta-small" rel="noopener noreferrer">Suggestions</a> </li> <li class="pl-20 pl-10@tp- inline-block"> <a data-ylk="elm:category;itc:0;" href="https://www.verizonmedia.com/policies/us/en/verizonmedia/privacy/index.html" target="_blank" class="t-meta-small th-meta-small" rel="noopener noreferrer">Privacy Policy (Updated)</a> </li> <li class="pl-20 pl-10@tp- inline-block"> <a data-ylk="elm:category;itc:0;" href="https://guce.engadget.com/privacy-dashboard?locale=en-US" target="_blank" class="t-meta-small th-meta-small" rel="noopener noreferrer">Privacy Dashboard <img class="w-15@s h-15@s h-22@m+ w-22@m+ inline vm" alt="privacy logo" src="https://s.blogsmithmedia.com/www.engadget.com/assets-h07750ff94825edf98d4aa04ff0d736bf/images/privacy.png?h=d61bd4381b75efdf07e827ad267213ea" /> </a> </li> <li class="pl-20 pl-10@tp- inline-block"> <a data-ylk="elm:category;itc:0;" href="https://www.verizonmedia.com/policies/us/en/verizonmedia/terms/otos/index.html" target="_blank" class="t-meta-small th-meta-small" rel="noopener noreferrer">Terms of Service (Updated)</a> </li> <li class="pl-20 pl-10@tp- inline-block"> <a data-ylk="elm:category;itc:0;" href="http://legal.aol.com/trademarks/" target="_blank" class="t-meta-small th-meta-small" rel="noopener noreferrer">Trademarks</a> </li> <li class="pl-20 pl-10@tp- inline-block"> <a data-ylk="elm:category;itc:0;" href="https://www.engadget.com/about/advertise" target="_blank" class="t-meta-small th-meta-small" rel="noopener noreferrer">Advertise</a> </li> </ul> </div> </div> </div> </div> </footer> </div> </div> </div> <div class="hide svg-icon-list"> <svg xmlns="http://www.w3.org/2000/svg"><symbol id="icon-alert" viewBox="0 0 29.6 29.6"><g fill="currentColor"><path d="M14.8 2c7.1 0 12.8 5.7 12.8 12.8s-5.7 12.8-12.8 12.8S2 21.9 2 14.8 7.7 2 14.8 2m0-2C6.6 0 0 6.6 0 14.8s6.6 14.8 14.8 14.8S29.6 23 29.6 14.8 23 0 14.8 0z"/><path d="M13.3 8.8h3l-.5 8h-2l-.5-8zm.5 10h2v2h-2v-2z"/></g></symbol><symbol id="icon-all" viewBox="0 0 36 36"><path fill="currentColor" d="M23 19h2c1.1 0 2-.9 2-2v-6c0-1.1-.9-2-2-2h-6c-1.1 0-2 .9-2 2v6h-7v-6c0-1.1-.9-2-2-2H2c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h6v7H2c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h6c1.1 0 2-.9 2-2v-6h7v6c0 1.1.9 2 2 2h6c1.1 0 2-.9 2-2v-6c0-1.1-.9-2-2-2h-6v-7h4zm-4-5v-3h6v6h-6v-3zM8 30v4H2v-6h6v2zm0-17v4H2v-6h6v2zm9 13h-7v-7h7v7zm6 2h2v6h-6v-6h4z"/></symbol><symbol id="icon-aol-tech" viewBox="0 0 168.272 39.167"><path fill="#9A9A9A" d="M104.889 5.836c0-1.096-.776-1.917-2.009-1.917H83.934c-1.232 0-2.009.822-2.009 1.917 0 1.05.776 1.872 2.009 1.872h7.168c.182 0 .273.092.273.274v25.019c0 1.369.822 2.191 2.055 2.191 1.187 0 2.009-.822 2.009-2.191V7.982c0-.183.091-.274.273-.274h7.168c1.233 0 2.009-.822 2.009-1.872zm51.673 6.757c-2.968 0-5.067 1.278-6.118 2.967h-.045V5.745c0-1.278-.822-2.055-1.918-2.055s-1.918.776-1.918 2.055v27.393c0 1.278.822 2.055 1.918 2.055s1.918-.776 1.918-2.055V21.496c0-3.242 2.054-5.342 5.021-5.342 3.287 0 4.885 2.055 4.885 5.57v11.414c0 1.278.822 2.055 1.918 2.055s1.917-.776 1.917-2.055V20.811c0-4.976-2.83-8.218-7.578-8.218zm-22.871 3.561c1.963 0 3.15.73 4.154 1.826.502.548 1.004.913 1.689.913.959 0 1.689-.73 1.689-1.689 0-.502-.184-.913-.549-1.415-1.369-1.78-3.697-3.196-6.984-3.196-4.2 0-7.396 2.1-8.629 5.889-.411 1.324-.685 3.104-.685 5.524 0 2.465.273 4.246.685 5.57 1.232 3.789 4.429 5.889 8.629 5.889 3.287 0 5.615-1.415 6.984-3.195.365-.502.549-.913.549-1.416 0-.958-.73-1.688-1.689-1.688-.686 0-1.188.365-1.689.913-1.004 1.096-2.191 1.826-4.154 1.826-2.374 0-4.154-1.142-4.885-3.424-.365-1.142-.503-2.512-.503-4.475 0-1.917.138-3.287.503-4.429.731-2.281 2.511-3.423 4.885-3.423zm-22.778-3.561c-4.154 0-7.305 2.1-8.537 5.889-.457 1.37-.685 2.922-.685 5.524s.273 4.154.73 5.524c1.232 3.835 4.291 5.935 8.812 5.935 3.377 0 5.98-1.278 7.851-3.013.365-.365.594-.822.594-1.37 0-.913-.685-1.598-1.598-1.598-.457 0-.867.183-1.324.548-1.644 1.232-3.104 1.872-5.25 1.872-2.693 0-4.702-1.278-5.524-3.789-.273-.868-.365-1.599-.365-2.648 0-.183.092-.273.274-.273h12.828c.914 0 1.507-.594 1.507-1.461 0-2.146-.228-3.835-.73-5.296-1.187-3.744-4.292-5.844-8.583-5.844zm5.112 9.541h-10.134c-.183 0-.274-.091-.274-.273 0-1.005.092-1.781.319-2.466.73-2.191 2.557-3.424 5.022-3.424 2.465 0 4.292 1.232 5.022 3.424.228.685.319 1.461.319 2.466.001.183-.091.273-.274.273zm-68.746-11.46c-8.505 0-12.59 6.725-12.59 12.479 0 5.753 4.085 12.479 12.59 12.479 8.504 0 12.587-6.727 12.587-12.479 0-5.755-4.083-12.479-12.587-12.479zm0 18.401c-3.032 0-5.6-2.498-5.6-5.922 0-3.427 2.568-5.922 5.6-5.922 3.03 0 5.599 2.495 5.599 5.922-.001 3.424-2.569 5.922-5.599 5.922zm16.541 5.9h7.082V3.668H63.82v31.307zm109.707-9.095a4.706 4.706 0 1 0-.001 9.413 4.706 4.706 0 0 0 .001-9.413zM15.606 3.668L3.272 34.975h8.357l1.65-4.527h10.872l1.525 4.527h8.442L21.911 3.668h-6.305zm-.126 20.433l3.28-10.915 3.279 10.915H15.48z"/></symbol><symbol id="icon-article" viewBox="0 0 17 13"><title>Page 1Page 1ear iconeye iconFill 23text filevr