0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

#193 Raycastでブラウザとローカルツールを連携してみる

0
Posted at

はじめに

最近、ランチャーアプリRaycastを使い始めたのですが、AppleScriptと組み合わせると想像以上の柔軟性で驚きました。

特に面白いと感じたのが、アプリ間連携ができる点です。
これまではChrome拡張機能のように、
1つのアプリ内で閉じた効率化を考えることが多かったのですが、
Raycast + AppleScript を使うことで、
アプリを跨いだ連携まで視野に入るようになりました。
今回は、ブラウザ<=>VSCodeの繋ぎこみをしてくれるコマンドをお試しで作りましたのでご紹介します。
Raycast活用アイディアの参考になれば嬉しいです。

環境について

本記事の動作環境(筆者環境): macOS / Raycast / Google Chrome / iTerm2 / ghq(リポジトリ管理)

なお、Raycast自体はWindows版も存在しますが、本記事のスクリプトはAppleScript(osascript)に依存しているので、そのままではWindowsでは動作しません。
Windowsで同等のことをする場合は、PowerShellやAutoHotkeyを使って似た構成が組めるかと思います。

内容

今回作成したスクリプトは、Chromeで現在開いてるGitHubページをVSCodeで開くというものです。
CloneやCheckoutも必要に応じて自動で行うようにしています。
また、表示していたのがファイルページだった場合は、そのファイルにフォーカスが当たるようにしています。

動作紹介

例として、VSCodeのリポジトリで試してみます。

まず、Chromeで該当のGitHubページを開きます。
タグ1.0.0README.mdを開きました。
https://github.com/microsoft/vscode/blob/1.0.0/README.md
image01.png

続いて、今回作成したRaycastスクリプト「Github to VSCode」を起動します
image02.png

Cloneが始まり、1.0.0にCheckoutしてREADME.mdが開いた状態でVSCodeが起動します。
image03.png

導入方法

Script Commandsを置くディレクトリを設定

Raycastを起動 → SettingsExtensions → 左下の+Add Script Directoryで、シェルスクリプトを置くフォルダを登録します。
image04.png

これで、登録したディレクトリ配下に置いた.shファイルがRaycastに自動的にコマンドとして認識されるようになります。

コード配置

設置したディレクトリに以下のコードを配置します。

github-to-vscode.sh
#!/usr/bin/env bash
# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title GitHub to VSCode
# @raycast.mode silent
# @raycast.packageName Browser

# Optional parameters:
# @raycast.description ChromeのGitHub URLからghqローカルクローン+VSCodeで開く

set -euo pipefail

notify() {
  local title="$1"
  local msg="$2"
  osascript -e "display notification ¥"$msg" with title ¥"$title""
}

# 1. Chromeの現在のタブのURLを取得
URL=$(osascript -e 'tell application "Google Chrome" to get URL of active tab of front window' 2>/dev/null || true)

if [[ -z "$URL" ]]; then
  notify "GitHub to VSCode" "Chrome に開いているタブがありません"
  exit 1
fi

# 2. github.com URLかチェック
if [[ ! "$URL" =~ ^https://github¥.com/ ]]; then
  notify "GitHub to VSCode" "GitHub のURLではありません"
  exit 1
fi

# 3. URLパース: owner/repo[/blob/<ref>/<path>][#L<line>]
PATH_PART="${URL#https://github.com/}"
PATH_PART="${PATH_PART%%¥?*}"
HASH_PART=""
if [[ "$PATH_PART" == *"#"* ]]; then
  HASH_PART="${PATH_PART##*#}"
  PATH_PART="${PATH_PART%%#*}"
fi

REST="$PATH_PART"
OWNER="${REST%%/*}"; REST="${REST#*/}"
REPO="${REST%%/*}"; REST="${REST#*/}"

# 4. blob URL なら AFTER_BLOB と行番号を切り出す
AFTER_BLOB=""
LINE_NUM=""
if [[ "$REST" == blob/* ]]; then
  AFTER_BLOB="${REST#blob/}"
  if [[ "$HASH_PART" =~ ^L([0-9]+) ]]; then
    LINE_NUM="${BASH_REMATCH[1]}"
  fi
fi

# 5. ローカルパス組み立て + cloneされていなければ ghq get
GHQ_ROOT=$(ghq root)
REPO_DIR="$GHQ_ROOT/github.com/$OWNER/$REPO"
if [[ ! -d "$REPO_DIR" ]]; then
  notify "GitHub to VSCode" "$OWNER/$REPO をclone中..."
  ghq get "github.com/$OWNER/$REPO"
fi

# 6. AFTER_BLOB から REF と FILE_PATH を切り出す (ローカルrepo の実在ref で最長一致)
REF=""
FILE_PATH=""
if [[ -n "$AFTER_BLOB" ]]; then
  git -C "$REPO_DIR" fetch origin --prune --quiet 2>/dev/null || true
  IFS='/' read -ra SEGS <<< "$AFTER_BLOB"
  NUM_SEGS=${#SEGS[@]}
  for ((n=NUM_SEGS; n>=1; n--)); do
    cand=""; for ((i=0; i<n; i++)); do cand="${cand:+$cand/}${SEGS[i]}"; done
    fpart=""; for ((i=n; i<NUM_SEGS; i++)); do fpart="${fpart:+$fpart/}${SEGS[i]}"; done
    if git -C "$REPO_DIR" rev-parse --verify --quiet "$cand" >/dev/null 2>&1 ¥
       || git -C "$REPO_DIR" rev-parse --verify --quiet "origin/$cand" >/dev/null 2>&1; then
      REF="$cand"; FILE_PATH="$fpart"; break
    fi
  done
fi

# 7. ファイル指定URLの場合は そのrefをcheckout (失敗したら停止)
if [[ -n "$REF" && -n "$FILE_PATH" ]]; then
  if ! CO_ERR=$(git -C "$REPO_DIR" checkout "$REF" 2>&1); then
    SAFE_ERR="${CO_ERR//¥"/¥¥¥"}"
    osascript -e "display notification ¥"checkout $REF 失敗: ${SAFE_ERR:0:200}" with title ¥"GitHub to VSCode¥""
    exit 1
  fi
fi

# 8. code起動 (リポジトリ全体 + 必要ならファイル指定)
if [[ -n "$FILE_PATH" ]]; then
  TARGET_FILE="$REPO_DIR/$FILE_PATH"
  if [[ -n "$LINE_NUM" ]]; then
    code "$REPO_DIR" -g "$TARGET_FILE:$LINE_NUM"
  else
    code "$REPO_DIR" -g "$TARGET_FILE"
  fi
  notify "GitHub to VSCode" "$OWNER/$REPO$FILE_PATH を開きました"
else
  code "$REPO_DIR"
  notify "GitHub to VSCode" "$OWNER/$REPO を開きました"
fi

処理の流れ

ざっくりと、こんな流れです。

AppleScript で active tab の URL を取得
 ⇒ owner/repo / (blob/<ref>/<file>) をパース
 ⇒ ghq root配下に repo がなければ ghq get
 ⇒ ファイルURLなら そのref を git checkout
 ⇒ code <repo-root> [-g <file>:<line>] でVSCode起動

スラッシュ入りブランチ名の扱い

GitHubのURLはhttps://github.com/owner/repo/blob/<ref>/<path>という形ですが、<ref>にもスラッシュが入りうるので、URLだけ見ても どこまでがブランチ名でどこからがファイルパスか は決めきれません。

たとえばhttps://github.com/owner/repo/blob/feature/foo/bar/baz.go

  • ref=feature / file=foo/bar/baz.go
  • ref=feature/foo / file=bar/baz.go
  • ref=feature/foo/bar / file=baz.go

のいずれの解釈もありえます。
これに対しては、ローカルクローンを先に取得しておいて、 実在するrefの中で最も長く一致するものを選ぶ という方針にしています。

IFS='/' read -ra SEGS <<< "$AFTER_BLOB"
NUM_SEGS=${#SEGS[@]}
for ((n=NUM_SEGS; n>=1; n--)); do
  cand=""; for ((i=0; i<n; i++)); do cand="${cand:+$cand/}${SEGS[i]}"; done
  fpart=""; for ((i=n; i<NUM_SEGS; i++)); do fpart="${fpart:+$fpart/}${SEGS[i]}"; done
  if git -C "$REPO_DIR" rev-parse --verify --quiet "$cand" >/dev/null 2>&1 ¥
     || git -C "$REPO_DIR" rev-parse --verify --quiet "origin/$cand" >/dev/null 2>&1; then
    REF="$cand"; FILE_PATH="$fpart"; break
  fi
done

git rev-parse --verify --quiet <名前>は、<名前>が実在するrefなら成功(exit 0)し、なければ失敗(exit 1)するので、これを最長セグメントから順に試して一致したところで採用します。

まとめ

今回はアプリを跨ぐようなRaycastのスクリプト作成例をご紹介しました。
例示したスクリプト自体の需要はあまりないと思いますが、面白いスクリプトを思いつくためのきっかけになれば嬉しいです。

最後まで読んでいただきありがとうございます。

おまけ

今回はURL取得もAppleScriptのプロパティ経由で済ませてしまいましたが、 アクティブタブに任意のJavaScriptを注入したり、その実行結果をAppleScript側で受け取ることも可能です。

URL=$(osascript -e 'tell application "Google Chrome" to get URL of active tab of front window' 2>/dev/null || true)

URL=$(osascript -e '
tell application "Google Chrome"
  tell active tab of front window
    return execute javascript "window.location.href"
  end tell
end tell' 2>/dev/null || true)

execute javascript "..."に渡した文字列はChromeのアクティブタブのJavaScriptランタイム上で実行され、戻り値がAppleScript側に返ります。

なお、Chromeでこれを使うには表示 → 開発/管理 → AppleEventsからのJavaScriptを許可を有効化する必要があります。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?