良きに計らうコピー&ペースト

選択範囲をコピーして、好みの場所に貼付ける。MacBookにおいてはcommand-Cとcommand-Vで実現される、あまりにも有り触れたこのシンプルな操作は、実はGUI環境のOSに究極の便利さをもたらす必須機能であると、以前、気付かされた。iPhone OS 3.0で、今まで出来なかったコピー&ペーストが実現された時、改めて当然の如く使っているこの機能の便利さを実感したのだ。(それまでが相当不便だった)

シンプルな操作とは裏腹に、この機能を実現するためには結構複雑な仕組みが必要らしい。その詳細を説明できるほどの知識はないが、一般的に考えても結構複雑なプロセスを辿る必要がありそう。

  • コピー&ペーストは、テキストに限らず、選択可能なあらゆるアイテム(対象オブジェクト)で当り前のように利用できる。
    • 修飾情報を持ったテキスト(リッチテキスト)、ファイル、画像、音楽、映像、表計算ソフトなど。
  • 当然、異なるアプリケーション間でもコピー&ペーストが可能だ。(それが便利さの所以)
  • しかし、アプリケーションが異なれば、扱うデータの形式も異なるはず。
  • それでも特に違和感を感じることなく利用できている背後には、コピーしたデータをペーストする時に、ペースト先に最適なデータ形式に変換(あるいはデータ取得先を変更)してくれているのだ。
  • 例:Finderでファイルを選択してコピーする。
    • Finder.appでペーストすると、同じファイルになる。
    • テキストエディット.appの標準テキスト形式にペーストすると、ファイル名のテキストになる。
    • テキストエディット.appの標準テキスト形式にドラッグ&ドロップ*1すると、ファイルパスになる。
    • ターミナル.appでペーストすると、ファイルパスのテキストになる。(しかも、半角スペースが含まれる場合は\でエスケープされる。)
    • プレビュー.appでクリップボードから新規作成またはペーストすると、アイコン画像になる。
  • 例:Safariで表示しているページのテーブル(表)の部分をコピーする。
    • テキストエディット.appの標準テキスト形式にペーストすると、タブ区切りのテキストになる。
    • テキストエディット.appのリッチテキスト形式でペーストすると、見た目もほとんど同じリッチテキストになる。
    • 表計算ソフトにペーストすると、データがセルごとに展開される。(何年か前、この事実を知った時、感動した!)

以上を見ると、コピー&ペーストした結果、全く違う形式に変換されていることを思い知らされる。それでも自然と馴染んで操作できるのは、それが多くのケースで人間が望む結果であるからなのだ。

自分が望む結果

アプリケーションが標準でサポートするコピー&ペーストは、基本的に万人向けのデータ変換だ。最大公約数の人が幸せになる形式に変換してくれる。しかし、それは自分が望む形式ではないかもしれない...。あるいは、特定の条件では別の形式に変換したい要望もあると思う。

  • Finderのファイルをコピー&ペーストした時に、ファイルパスのテキストにしたい。(テキストエディタにドラッグ&ドロップした時のように。半角スペースのエスケープなしで。)
  • Webページをコピー&ペーストした時に、aタグのリンクを生成したい。表示するテキストは、そのページのタイトルにしたい。
  • AppleScriptエディター.appのスクリプトをコピー&ペーストした時に、シンタックスハイライトなHTMLを生成して、同じ見た目にしたい。

何だか自分がこのブログを書く時に必要な操作ばかりなのだ。そうでない人には不要な機能だと思う。しかし、自分にとってはこのような機能が揃っていれば、最高に幸せな環境だ。

上記機能は、ドラッグ&ドロップに操作方法を変えたり、単機能のアプリケーション、あるいはAppleScriptで処理すれば、すでに実現可能な機能だ。しかし、問題は処理したいと思った時に、ストレスなく、素早く、実行できる環境が欲しいのだ。

  • もし、Webページのテーブル(表)データを、表計算ソフトのセルごとに展開したい時、コンバーターが必要だとしたら、どうだろうか?
  • ファイルパスのテキストが欲しい時、毎回、目的のカーソル位置にドラッグ&ドロップするのは結構しんどい。

ショートカットの問題

そこで、必要最小限の機能を持ったAppleScriptを作って、それをショートカットで呼び出せるようにすれば、かなり満足な環境に近付く。実際、自分が欲しい上記機能は、すべてショートカットで処理できる環境になっていた。そのようにして、どんどん、便利だと思うアプリケーションの機能やAppleScriptをショートカットに登録してきた。

すると今度は別の悩みが発生した。ショートカットが増え過ぎてしまったのだ...。その結果、忘れてしまったり、操作を間違えたり、重複する別の機能が反応してしまったり、素早く呼び出すことが目的のショートカット操作に悩んでモタついてしまう、という本末転倒なことになってきた...。

付加情報で賢く判定する

そこで、コピーに関連する操作には、機能が違っても同じショートカットを割り当ててしまうことを考えた。コピーする時の状況の違いで、欲しい機能を賢く判定するのだ。

  • 具体的には、以前、複数のクリップボードを扱いたいと思って、AppleScriptで拡張した。
    • control-shift-1でクリップボード1にコピー。control-option-1でクリップボード1をペースト。
    • 同じようにcontrol-shift-2、3、4...とショートカットを割り当てて、複数のクリップボードとして利用していた。
    • そして、このショートカットでファイルをコピーした時は、必ず、ファイルパスをコピーする仕様にしてしまった。
    • もし、通常のコピーをしたければ、お馴染みのcommand-Cを利用すれば良いのだし、その時に複数のクリップボードが必要な状況はほとんどないので。(自分の使い方では)
  • また、コピーする時には必ず何かを選択する必要がある。(何も選択していない状況では、"コピー"メニューはグレー表示され、操作不能になっている)
  • その仕組みを利用して、何も選択しないcontrol-shift-1でのコピー操作と、その時アクティブなアプリケーションを見て、好みの処理を割り振ることも出来る。
  • そう、何も選択していない状況も立派な付加情報であり、アクティブなアプリケーションと組み合わせて、自分にとってベストなデータを取得するための判定基準になると気付いた。 

複数クリップボードに拡張するAppleScript

以上の方針で作成したのが、以下のスクリプト。複数のファイルに分かれている。

clip1_copy.scpt
  • まずはコピー操作(control-shift-1)のショートカットに反応する入り口のスクリプト
  • 実際のコピー処理は次の_clip_copy.scptで実行する。
  • このスクリプトは、押されたショートカットが何であるか判定するためにある。(複数クリップボードを扱うので)
  • clip2_copy.scpt、clip3_copy.scpt、...という具合にファイル名だけ変更して増やして行けば、その分クリップボードも増えるのだ。


delay 0.2 --Quickslverで実行するにはひと呼吸必要だった
set script_path to (path to scripts folder as text) & "clip:_clip_copy.scpt"
run script file script_path with parameters (path to me)

_clip_copy.scpt
  • 引数でクリップボードを区別して、実際のコピー処理を実行するスクリプト
  • コピーする時の状況を判定して、run scriptでファイル指定して、個別の処理を実行している。
  • ここに条件判定を追加して、AppleScriptによる処理を別ファイルに作成すれば、新たなスペシャルコピーとして簡単に拡張できるのだ。


(* クリップボードにコピーする
*)
property LIB : load script file ((path to scripts folder as text) & "_lib.scpt")
property GUI : load script file ((path to scripts folder as text) & "_gui.scpt")
property CLIP : load script file ((path to scripts folder as text) & "clip:_clip_base.scpt")

--on run (args)--だと、argsはリストになる -> CLIP's clipName(args's item 1)
on run {args} --だと、argsはアイテムになる -> CLIP's clipName(args)
set current_clip to (the clipboard as record)
set the clipboard to ""

set appName to GUI's frontmost_app()
if appName is "Finder" then
run script file ((path to scripts folder as text) & "clip:_copy_path.scpt")
else
GUI's shortcut("", "command-C")
delay 0.5
end if

if (the clipboard) is "" then
if appName is "AppleScript Editor" then
run script file ((path to scripts folder as text) & "clip:_rtf_to_html.scpt")
else
run script file ((path to scripts folder as text) & "clip:_copy_a_link.scpt")
end if
end if

LIB's message(CLIP's clipName(args), the clipboard as text)

set CB to load script file (CLIP's clipPath(args))
set CB's pb to (the clipboard as record) --すべて

store script CB in file (CLIP's clipPath(args)) replacing yes
delay 1
set the clipboard to current_clip
end run

_clip_base.scpt
  • コピー処理(clip1_copy.scpt)・ペースト処理(clip1_paste.scpt)、両方に共通のスクリプトをまとめた。


(* クリップボードのコピー・ペースト共通のスクリプト
*)

on clipName(aPath) tell application "Finder"
set myName to aPath's name as text
item 1 of my split(myName, "_") end tell
end clipName

on clipFolder(aPath) tell application "Finder"
aPath's folder as text
end tell
end clipFolder

on clipPath(aPath) clipFolder(aPath) & clipName(aPath) & ".scpt"
end clipPath

on split(sourceText, delimiter) if sourceText = "" then return {} set oldDelimiters to AppleScript's text item delimiters
set AppleScript's text item delimiters to {delimiter} set theList to text items of sourceText
set AppleScript's text item delimiters to oldDelimiters
return theList
end split

clip1.scpt


--クリップボード保存用のスクリプトファイル
property pb : ""

clip1_paste.scpt
  • ペースト操作(control-option-1)のショートカットに反応する入り口のスクリプト
  • 実際のペースト処理は次の_clip_paste.scptで実行する。
  • このスクリプトは、押されたショートカットが何であるか判定するためにある。(複数クリップボードを扱うので)


delay 0.2 --Quickslverで実行するにはひと呼吸必要だった
set script_path to (path to scripts folder as text) & "clip:_clip_paste.scpt"
run script file script_path with parameters (path to me)

_clip_paste.scpt


(* クリップボードをペーストする
*)
property LIB : load script file ((path to scripts folder as text) & "_lib.scpt")
property GUI : load script file ((path to scripts folder as text) & "_gui.scpt")
property CLIP : load script file ((path to scripts folder as text) & "clip:_clip_base.scpt")

--on run (args)--だと、argsはリストになる -> CLIP's clipName(args's item 1)
on run {args} --だと、argsはアイテムになる -> CLIP's clipName(args)
set current_clip to (the clipboard as record)
set CB to load script file (CLIP's clipPath(args))
set the clipboard to CB's pb

GUI's shortcut("", "command-V")

delay 1
set the clipboard to current_clip
end run

コピーの時、特別なデータ変換をするスクリプト

  • 上記の_clip_copy.scptでコピー時の状況を判定して、必要に応じて以下の特別なデータ変換をしてコピーする。(クリップボードに保存する)
  • 変換したデータをクリップボードに保存することで、呼び出し元の_clip_copy.scptが拡張クリップに保持してくれる。
_copy_path.scpt
  • Finderのファイルパスをコピーする。(複数選択もOK)


(* Finderパスをコピーする
*)
property LIB : load script file ((path to scripts folder as text) & "_lib.scpt") --property GUI : load script file ((path to scripts folder as text) & "_gui.scpt")

set AppleScript's text item delimiters to return
tell application "Finder"
set path_text to the selection as text
set disk_name to startup disk as text
end tell
set AppleScript's text item delimiters to ""

--起動ディスク(例 Macintosh HD:)のパスを:に置き換え
set path_text to LIB's replace(path_text, disk_name, ":")
--先頭の":"を削除
if path_text starts with ":" then
set path_text to text 2 thru -1 of path_text
end if

set the clipboard to POSIX path of path_text

_copy_a_link.scpt
  • Webページへのaタグリンクとしてコピーする。


(* WEBページから、<a href="...">...</a>リンクを生成する
*)
--property LIB : load script file ((path to scripts folder as text) & "_lib.scpt")
property GUI : load script file ((path to scripts folder as text) & "_gui.scpt")
try
set app_name to GUI's frontmost_app() set theURL to run script "tell application \"" & app_name & "\" to document 1's URL"
set theTitle to run script "tell application \"" & app_name & "\" to document 1's name"
on error
GUI's init() GUI's shortcut("", "command-L") GUI's shortcut("", "command-C") set theURL to the clipboard as text
GUI's shortcut("", "esc") if theURL is "" then error -127 --このスクリプト、親スクリプト、すべて中止。
--if theURL is "" then return --このスクリプトのみ中止。親スクリプトは継続。
GUI's shortcut("", "command-D") GUI's shortcut("", "command-C") GUI's shortcut("", "esc") set theTitle to the clipboard as text
end try

set the clipboard to "<a href=\"" & theURL & "\">" & theTitle & "</a>"

_rtf_to_html.scpt


以上で、同じ拡張コピー操作で、自分が望む処理を素早く実行できるようになった!AppleScriptが良きに計らって処理してくれるのだ。自分にとっては、かなり自然に操作できるようになった。満足。

ダウンロード

      • 変更履歴を追うために、ファイル.txtとファイル.scptが重複していますが、AppleScriptとしてすぐ実行できるのはファイル.scptです。

*1:アーロン・ヒレガス曰く「ドラッグ&ドロップは、派手なコピー&ペースト以外の何者でもない」と。