Open Xcode project and set active file from terminal - xcode

Opening an Xcode project from the terminal is easy:
open Foo.xcodeproj/
But that just opens the project and resumes its previous state using UserInterfaceState.xcuserstate - so it just opens to the last active file you were editing.
Is there a way to open an Xcode project and specify which file it should open to?
What I've tried:
Editing .xcuserstate - nightmare, don't do it.
Running open Foo/Foo.xcodeproj/ then open Foo/Sources/main.swift which works some of the time, but not always. (If you just generated the project and do this, it'll open the project, then in a separate window it'll open the file.)
Any other ideas?

An Xcode engineer named Mike pointed me at the loaded property of Xcode's workspace document scripting class. By polling this, we can wait until Xcode has finished loading the project (including loading the editor pane) before asking it to open the file. This lets up reliably open the file in its project's window.
Here's the xopen script I wrote:
#!/bin/bash
shopt -s nullglob
sourceFile="$1"
case "$sourceFile" in
/*) ;;
*) sourceFile="$PWD"/"$sourceFile" ;;
esac
projectDir="$sourceFile"
while [[ $projectDir = */* ]]; do
projectDir="${projectDir%/*}"
candidates=("$projectDir"/*.xcodeproj)
candidate="${candidates[0]}"
if [[ "$candidate" != "" ]]; then
jPath="$candidate"
fi
done
if [[ "$jPath" = "" ]]; then
echo 1>&2 "error: couldn't find .xcodeproj in any parent directory"
exit 1
fi
exec osascript - "$jPath" "$sourceFile" <<EOF
on run argv
set jPath to item 1 of argv
set sourceFile to item 2 of argv
tell app "Xcode"
set wsDoc to (open jPath)
set waitCount to 0
repeat until wsDoc's loaded or waitCount ≥ 20
set waitCount to waitCount + 1
delay 1
end repeat
if wsDoc's loaded then
open sourceFile
end if
end tell
end run
EOF
This script uses the shell to walk up the directory tree from the source file (given as a command line argument) until it finds a directory containing an Xcode project package. Then it passes the path to the project and the path to the source file to an AppleScript. The AppleScript asks Xcode to open the project. If Xcode already has the project open, it'll just bring the existing project window to the front.
Next, the script polls Xcode until it reports that the workspace document is loaded, or until 20 seconds have elapsed.
Finally, if the workspace document is loaded, it asks Xcode to open the source file. Xcode will open the source file in the existing project window's editor.

Related

MacOS Terminal: Is it possible to create a "drag-and-drop" action via script?

There are many posts that explain how to drag-and-drop things into an open Terminal window. But what I would like to do is to tell Terminal to drag-and-drop a previously selected directory onto another application like VSCode or Renamer. I have not found any documentation for that. Is it at all possible? And if so, would somebody please point me to a documentation?
UPDATE:
I'd like to clarify my question with what I intend to do:
Pre requisites:
a "work folder" contains folders and files that shall be renamed
the renaming is done by an application called "A better finder renamer" (which allows presets)
An "Automator" (MacOS app) action shall imitate these steps:
the "work folder" is right clicked
the folder is drag-and-dropped onto the ABFR, which initiates the currently active preset
other actions via bash (like 'mv .//.* ./') ...
It is the "drag-and-drop" part of the Automator action that presents a riddle for me.
The "drag-and-drop" operation is manual operation. In AppleScript, instead the command to open the file or folder is given to the destination application itself.
Second thing to keep in mind. Getting Terminal's current working directory is easy with its do script "pwd" command. But the result of the do script Terminal command returned to the script is always the window tab, not the result of the pwd shell command. One solution is to redirect (remember) the result of pwd in a temporary text file.
set tempFolder to (path to temporary items folder from user domain)
set temp to POSIX path of tempFolder & "workingDirectory.txt"
tell application "Terminal" to do script ("pwd > " & temp) in selected tab of window 1
set curDirPosixPath to paragraph 1 of (read file ((tempFolder as text) & "workingDirectory.txt"))
set curDirHFSPath to curDirPosixPath as POSIX file as Unicode text
tell application "Visual Studio Code" to open curDirHFSPath
.
NOTE: other possible solution (I don't like) is parsing the text contents of Terminal window after pwd command execution. You can get contents using property contents (of selected tab of front window).
Open Automator, choose create New Document.
Choose create new Quick Action (service).
Set workflow receives current folders in any application.
From library Add Run AppleScript action. Edit it contents:
on run {input, parameters}
set curDirHFSPath to (item 1 of input) as text
tell application "Visual Studio Code" to open curDirHFSPath
end run
Save this Quick Action as service. Now when right-clicking the folder, opens the context menu, where you can choose and run this service.

How to get clang format for Xcode 8?

After Xcode update to version 8. The very useful Alcatraz PlugIn Manager is locked out and superb utilities like clang-format, or highlight selected word occurrences, or resize the font by use of a shortcut are gone.
How can I reenable clang-format to format my current source code file on save with a template .clang-format in any parent directory of the source file?
You could create a shell script that is added to Xcode 8 as a behavior: Xcode > Behaviors > +(to create new one) > Run script: (select file here), add shortcut like Cmd+Shift+S.
The script asks Xcode to save the current document. Then it extracts its filepath and calls clang-format to format that file in-place. Clang-format has to be available e.g. by using brew as the package manager to download it and having its path published for command line access. As usual the style guide used by clang-format must have the name .clang-format and must be in any parent folder of the source file.
Here is the script:
#!/bin/bash
CDP=$(osascript -e '
tell application "Xcode"
activate
tell application "System Events" to keystroke "s" using {command down}
--wait for Xcode to remove edited flag from filename
delay 0.3
set last_word_in_main_window to (word -1 of (get name of window 1))
set current_document to document 1 whose name ends with last_word_in_main_window
set current_document_path to path of current_document
--CDP is assigned last set value: current_document_path
end tell ')
LOGPATH=$(dirname "$0")
LOGNAME=formatWithClangLog.txt
echo "Filepath: ${CDP}" > ${LOGPATH}/${LOGNAME}
sleep 0.6 ### during save Xcode stops listening for file changes
/usr/local/bin/clang-format -style=file -i -sort-includes ${CDP} >> ${LOGPATH}/${LOGNAME} 2>&1
# EOF
Please exchange the path /usr/local/bin to the one where your clang-format executable resides.
Happy coding!
The mapbox/XcodeClangFormat extension looks like a promising way to get clang format working with Xcode8.
Due to the limitations of source editor extensions, unfortunately you can only specify one .clang-format file for all your projects. "Format on save" also is not available.
Found a viable solution in this blog - code-beautifier-in-xcode
Basically, we can have clang-format running as a service by automator and invoke it through Xcode whenever we need to format the code. Refer the blog for more details.
Unfortunately your little script often does not update the formatted file in Xcode because it stops listening to file updates when saving. Increasing the sleep durations in the script does not make it more reliable and introduces a lot of waiting time for the common file-save & file-format action.
What I did in your situation was to get my mac backup and restore macOS and Xcode to the last version where all the productivity plugins from Alcatraz work fine again. This boosted my productivity.
It looks like Alcatraz plug-ins get be back to work in Xcode 8+ when unsigning them. Because I am not in the situation to try that, I can only point you to that resource:
Examine the header Installation on that github page ClangFormat-Xcode.

Opening Finder from terminal with file selected

I want to open Finder from the terminal with a specific file selected. I know that by using open . I can open the current directory in Finder, but I also want to select some file in the Finder window.
The basic thing I want to do is run a script that randomly selects a file among many in a folder and for that I need to open a new Finder window with the file selected.
The . in your open . command just means path at current location (which would be a folder) so open decides that the correct application to use is Finder. If you were to do open myTextFile.txt which is at your current location in the terminal open will decide to use a text editor instead. You can however specify the application to open the file with by using the -a flag so your command would look like this: open -a Finder myTextFile.txt.
What Faisal suggested will also work, the -R flag is an equivalent to using ⌘↩ (Command Return) in Spotlight.
this and some other nice shell tricks with the open command are described in this post: Shell tricks: the OS X open command
For me, code below works fine.
open -R your-file-path
You can do it like that
osascript -e "tell application \"Finder\"" -e activate -e "reveal POSIX file \"<your file path>\"" -e end tell

Open plain text files with LightTable by default when double-clicking them (in ubuntu 14.04 64bits)

I put LightTable in /opt/LightTable
By default, when you install the text editor LightTable in ubuntu 14.04 64 bits, you don't have an "open with LightTable" when you right-click the file you want to open with it.
Therefore I created a file /home/theuser/.local/share/applications/LightTable.desktop containing :
[Desktop Entry]
Name=LighTable Text Editor
Comment=Edit text files
Exec=/opt/LightTable/LightTable %f
Terminal=false
Type=Application
Icon=/opt/LightTable/core/img/lticon.png
Categories=Utility;TextEditor;
StartupNotify=true
MimeType=text/plain
so that a "open with LightTable" appears when I want open a file with LighTable. Now, the problems begin. When I do this, it only opens LightTable, as if I only ran the script
/opt/LightTable/LightTable
Therefore I went to see the script :
#!/bin/bash
BIN=ltbin
HERE=`dirname $(readlink -f $0)`
LIBUDEV_0=libudev.so.0
LIBUDEV_1=libudev.so.1
add_udev_symlinks() {
# 64-bit specific; look for libudev.so.0, and if that can't be
# found link it to libudev.so.1
FOLDERS="/lib64 /lib/x86_64-linux-gnu /usr/lib/x86_64-linux-gnu /usr/lib64 /usr/lib /lib"
for folder in $FOLDERS; do
if [ -f "${folder}/${LIBUDEV_0}" ]; then
return 0
fi
done
for folder in $FOLDERS; do
if [ -f "${folder}/${LIBUDEV_1}" ]; then
ln -snf "${folder}/${LIBUDEV_1}" "${HERE}/${LIBUDEV_0}"
return 0
fi
done
echo "${LIBUDEV_0} or ${LIBUDEV_1} not found in any of ${FOLDERS}".
exit 1
}
add_udev_symlinks
ARGS="$#"
CORRECTED=${ARGS//-/<d>}
CORRECTED=${CORRECTED// /<s>}
if [ -t 0 ] && [ $# != 0 ]; then
#We're in a terminal...
LD_LIBRARY_PATH="$HERE:$LD_LIBRARY_PATH" $HERE/$BIN "<d><d>dir=`pwd`<s>$CORRECTED" &
else
#We were double clicked
LD_LIBRARY_PATH="$HERE:$LD_LIBRARY_PATH" $HERE/$BIN &
fi
and replaced its ending if/else/fi by a simple :
LD_LIBRARY_PATH="$HERE:$LD_LIBRARY_PATH" $HERE/$BIN "<d><d>dir=`pwd`<s>$CORRECTED" &
so that the right behaviour (the one when opening a file from the terminal) is chosen.
This almost made my day. Now, if I double-click a file or right-click it and select "open with LightTable", the file is indeed opened in LightTable... but : this is true only if the file name and the path to the file have no blank space in their names.
If the file named "the file" is in the path thepath without space in it, when I double-click it, it opens two blank files "the" and "file" in LightTable. The same behavior is constated if thepath has space(s) in it.
Would someone have an idea ? I guess I should correct the bash script, but I'm not an expert in it. (I am not even sure that the script is really wrong...)
Thanks in advance
MEF
Your question has a tl;dr quality about it.
When you store "$#" in a variable, you really have to use an array and lots of quotes to preserve the elements with whitespace:
ARGS=("$#")
CORRECTED=("${ARGS[#]//-/<d>}")
CORRECTED=("${CORRECTED[#]// /<s>}")
But then the way you have to pass the args is a problem:
LD_LIBRARY_PATH="$HERE:$LD_LIBRARY_PATH" $HERE/$BIN "<d><d>dir=`pwd`<s>$CORRECTED" &
It's impossible to expand the array into a single space-delimited string and then somehow extract the elements that have significant whitespace.
You may have to do this, and see if it works:
export LD_LIBRARY_PATH="$HERE:$LD_LIBRARY_PATH" # might as well put on own line
"$HERE/$BIN" "<d><d>dir=`pwd`<s>" "${CORRECTED[#]}" &
# ...........^^^^^^^^^^^^^^^^^^^^ standalone argument
Actually, this is a bug in LightTable.
I opened an issue (https://github.com/LightTable/LightTable/issues/1762) and submitted a patch (https://github.com/LightTable/LightTable/pull/1763) to fix this :
There are 2 issues here:
currently the deployed Bash script doesn't pass any arguments to LightTable if it's not invoked from a terminal, but this is needed e.g. to make a gnome desktop shortcut. This issue can also be reproduced by using the ALT+F2 launcher under Ubuntu.
independently LightTable cannot currently open files whose names contain ' ' characters.

Create a launcher for a node.js script

I'm trying to create a launcher for node.js scripts (so that I can run the scripts by clicking on their file icons instead of launching them from the terminal.) How is this usually done? I'd prefer if I could simply run a script in the terminal by clicking on its icon.
I tried writing a shell script to launch another script in the same folder, but it doesn't show the node.js script's command line output for some reason:
#!/bin/bash
echo -n "Enter a node.js script to run > "
read text
node "$text"
I now know that you're looking for an Ubuntu solution, but in case someone is interested in an OS X solution, here goes:
Open Automator.
Create a new application.
Add an AppleScript action
Paste the following code:
on run {input, parameters}
tell application "Terminal"
repeat with f in input
do script "node " & quoted form of (POSIX path of f)
end repeat
activate
end tell
end run
Save the application.
In Finder, control-click any *.js file and select Open With > Other ..., pick the new application and check 'Always Open With.'
From then on, whenever you open a *.js file, it will open in a new Terminal window that will stay open after node finishes running; add ; exit to the command string above to close automatically (possibly adding read -sn 1 first to wait for a keystroke first.)
i use this to start my node scripts on debian in the terminal
#!/usr/bin/env sh
dir=$(dirname $0)
script="$dir/path_to_your_server.js"
echo "node $script"

Resources