それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

Sublimteテキストに貼り付ける時、フルパスで貼り付けたい

Sublime Textにペーストするとファイル名になる。

iTermのように、フルパスで貼り付けたい

f:id:takuya_1st:20150108195352j:plain

iTermなら、ペースト、フルパスになるじゃん?Sublime Text でも同じことをしたかったんですね。

クリップボードにあるデータのフルパスを取得する必要がある。

OSXクリップボードをしらべながらやってみた。

OSX JavaScriptでサービスを作るアプローチ

OSX JavaScript から クリップボードを取得する方法

#!/usr/bin/env osjscript

var app = Application("Finder")
app.includeStandardAdditions=true
ret = app.theClipboard()
console.log(ret);

a =  app.clipboardInfo()

console.log(JSON.stringify(a));

しかし、これでは、フルパスは取れない。Pathオブジェクトにかけると、カレントパスが出てくる

console.log(ret);⇛  console.log(Path(ret));

takuya@rena:/Applications$ osjscript ~/Desktop/paste.js
/Applications/true
[[null,4],[null,281],[null,258],[null,349],[null,10],[null,144],["ctxt",4],[null,22],[null,4],[null,8],[null,144],[null,22]]

このざま!

Sublime Text 3のAPIを叩いてみるアプローチ

Sublime Text 3で、ctrl + ` ( shift + @ ) を押して、コンソールを起動して、APIを叩く

>sublime.get_clipboard()

f:id:takuya_1st:20150108195555p:plain

これで取れそう?⇛だめ。

じゃぁ、Sublime Text 3 のAPIをハックしてみよう

/Applications/Sublime Text 3.app/Contents/MacOS/sublime.py

get_clipboard のメソッドをハックして、上書きすればいいんじゃね?

f:id:takuya_1st:20150108195619p:plain

だめ。出来ない。 sublime_api を呼んでいて、 デリゲートしてる。sublime_api の get_clipboard を上書きするのも面倒だし、 sublime_api.get_clipboard をコールしたら、すでにプレーンテキスト、ペーストボードがテキスト化された状態で出てくるので手が出せない。

じゃぁAutomatorで対応すればいいんじゃない?

だったら、Automatorで、フルパスでコピーして貼り付ければいいんじゃない? f:id:takuya_1st:20150108195601p:plain

一応出来る。

でもやりたいのは、ファイルパスをクリップボードにコピーした状態で、貼り付けたら、フルパスに変換して取り出すやつ。

そもそも、OSX の ペーストボードってどうなってるの?

Python で、クリップボードを取得すると。。。。

#!/usr/bin/python

from AppKit import *

pb = NSPasteboard.generalPasteboard()
pbstring = pb.stringForType_(NSStringPboardType)

print u"Pastboard string: %s".encode("utf-8") % repr(pbstring)

以上のような感じで取得することが出来る。

takuya@rena:~/Desktop$ echo こんにちは | pbcopy
takuya@rena:~/Desktop$ python paste_sample.py
Pastboard string: u'\u3053\u3093\u306b\u3061\u306f\n'

ただ、pythonでやると、import Appkit がやたら遅い。ペーストコマンドの上書きには現実的なアプローチじゃない。

MacRubyなら!速いんじゃない?

#!/usr/bin/env macruby
# encoding: UTF-8
framework 'Cocoa'
puts NSPasteboard.generalPasteboard.pasteboardItems
  .map { |pbi| pbi.stringForType('public.file-url') }.compact

  .map { |url| NSURL.URLWithString(url).path }

これは、バンドルだけあって、速い!!!!!

コレ最強。

public.file-url ってなに?⇛UTI

UTI : uniformed type identifier

というもので、OSXでは古くから、クリップボードの種類を識別して、アプリケーション側で適切なデータを取り出すようになっている。

つまり、WebkitでHTMLをコピーした時、クリップボードにはHTMLとプレーンテキストが入っていて、どちらでも好きな方をプログラム側で取り出してくださいってことね。

クリップボード(ペーストボード)を扱う、コマンドライン・アプリケーションを作る。

UTI をしらべるために、ちょっとペーストボードを扱うCMDアプリケーションを作ってみた

AppkitをLinkに追加して、っと

f:id:takuya_1st:20150108195627p:plain

インポートで、参照を追加

#import<Foundation/Foundation.h>
#import<AppKit/Appkit.h>

ペーストボードを取得して

NSPasteboard*pasteboard = [NSPasteboardgeneralPasteboard];
NSPasteboardItem*a = [pasteboardpasteboardItems].firstObject;

複数入ってるから、先頭を取得して、

NSArray*t = a.types;

クリップボードのデータのタイプを取得する

中身を見れば、OK

フォルダ・ファイルをクリップボードに入れてる時は

(
    "public.file-url",
    "com.apple.icns",
    "public.utf16-external-plain-text",
    "public.utf8-plain-text"
)

画像をクリップボードに入れてる時(スクショをクリップボードに入れた時)

 (
    "public.png"
)

HTML(ブラウザのHTML Body内でコピー)をクリップボードに入れてた時

(
    "public.utf8-plain-text",
    "public.html"
)

ブラウザのアドレスバーをクリップボードに入れてた時

 (
    "dyn.ah62d4rv4gu8zs3pcnzme2641rf4guzdmsv0gn64uqm10c6xenv61a3k",
    "dyn.ah62d4rv4gu8yc6durvwwaznwmuuha2pxsvw0e55bsmwca7d3sbwu",
    "public.utf8-plain-text",
    "public.url",
    "public.url-name"
)

今回欲しいのは、ファイルのパスですね。

ファイル(フォルダ)をコピーすると4種類のデータがペーストボードに入ってる。

(
    "public.file-url",
    "com.apple.icns",
    "public.utf16-external-plain-text",
    "public.utf8-plain-text"
)

プログラム側で、この中から、選んで、取得する必要があるらしい。

フォルダをコピーしたには、テキストやアイコンまで入ってる模様。

たとえば、アイコンを取り出してみる。

NSPasteboard*pasteboard = [NSPasteboardgeneralPasteboard];NSPasteboardItem*a = [pasteboardpasteboardItems].firstObject;
NSData*d = [adataForType:@"com.apple.icns"];
NSLog(@"%@", d );

f:id:takuya_1st:20150108195645p:plain

やばいほど、出力が長い。。。ま、しっかりアイコンが取り出せる事がわかる。

おなじクリップボードから、テキストとして取り出してみる。

NSPasteboard*pasteboard = [NSPasteboardgeneralPasteboard];NSPasteboardItem*a = [pasteboardpasteboardItems].firstObject;
NSData*d = [adataForType:@"public.utf8-plain-text"];
NSLog(@"%@", d );
NSString*str = [[NSStringalloc]initWithData:dencoding:NSUTF8StringEncoding];
NSLog(@"%@",str );

直接NSStringで取り出せるが、とりあえずNSDataにしてある。

f:id:takuya_1st:20150108195657p:plain

ファイル名を取り出してみる。

NSPasteboard*pasteboard = [NSPasteboardgeneralPasteboard];NSPasteboardItem*a = [pasteboardpasteboardItems].firstObject;

NSData*d = [adataForType:@"public.file-url"];
NSString*str = [[NSStringalloc]initWithData:dencoding:NSUTF8StringEncoding];
NSLog(@"%@",str );

f:id:takuya_1st:20150108195703p:plain

取り出せるけど、ちょっと期待と違うかも。

ファイル名が file:// から始まっていて、URLエンコードされているのをなんとかする。

NSPasteboard*pasteboard = [NSPasteboardgeneralPasteboard];NSPasteboardItem*a = [pasteboardpasteboardItems].firstObject;
NSData*d = [adataForType:@"public.file-url"];
NSString*str = [[NSStringalloc]initWithData:dencoding:NSUTF8StringEncoding];
NSLog(@"%@",str );
NSURL*u = [NSURLURLWithString:str];
NSLog(@"%@",u.path);

f:id:takuya_1st:20150108195708p:plain

無事取り出せた。これで、ファイルをクリップボードに入れた時に、テキストではなく、フルパスで取り出す方法がわかる。

これで、ペーストボードの仕組みが少しわかった。 ま、Windowsとかも同じようなものだし。クリップボードには複数の選択肢が入っていてアプリ側が自分にあった物を取り出す。

これで、さっきの macruby に戻って

#!/usr/bin/env macruby
# encoding: UTF-8
framework 'Cocoa'
puts NSPasteboard.generalPasteboard.pasteboardItems
  .map { |pbi| pbi.stringForType('public.file-url') }.compact
  .map { |url| NSURL.URLWithString(url).path }

この仕組みがよく理解できたので、そのまま使っても困らなさそうだ。

クリップボード複数のファイルが入った状態で、実行すると。。。

/Users/takuya/Desktop/pasteboard.rb
takuya@rena:~/Desktop$ macruby pasteboard.rb 2> /dev/null

/Users/takuya/Desktop/paste.js
/Users/takuya/Desktop/クリップボード
/Users/takuya/Desktop/スクリーンショット 2014-12-25 15.55.17.png
/Users/takuya/Desktop/pasteboard.rb

ちゃんと複数ファイルのフルパスを取り出すことが出来た。

ここまで出来たものを、ワンライナー化して。

macruby -W0  -e "framework 'Cocoa';puts NSPasteboard.generalPasteboard.pasteboardItems.map {|pbi|pbi.stringForType('public.file-url')}.compact.map {|url|NSURL.URLWithString(url).path}"

これを、Pythonから実行する

#!/usr/bin/env python
import subprocess

cmd = "/usr/local/bin/macruby -W0  "+ \
      " -e \"framework 'Cocoa';"+ \
      "puts NSPasteboard.generalPasteboard.pasteboardItems.map{|pbi|"+\
      "  pbi.stringForType('public.file-url')}.compact.map{|url|"+\
      "  NSURL.URLWithString(url).path"+\
      "}\" "
ret =  subprocess.check_output( cmd  )

Pythonから実行できたら、SublimeTextの拡張パッケージとして作りなおす

f:id:takuya_1st:20150108195713p:plain

新規プラグインとして、作って。

run_commandで実行

import sublime, sublime_plugin
import subprocess

class PasteAsPathCommand(sublime_plugin.TextCommand):
  def run(self, edit):
    cmd = ["/usr/local/bin/macruby",
            "-W0",
            "-e",
            "framework'Cocoa';"+\
            "a=NSPasteboard.generalPasteboard.pasteboardItems;"+\
            "a.map!{|pbi|pbi.stringForType('public.file-url')};"+\
            "a.compact!;"+\
            "a.map!{|url|NSURL.URLWithString(url).path};"+\
            "puts(a)"
    ]
    try :
      ret =  subprocess.check_output( cmd )
      if not ( len(ret) > 0 ) :
        ret = sublime.get_clipboard()
      else:
        ret = ret.decode("utf-8")
      # self.view.insert(edit, self.view.sel()[0].begin(), "Hello, World!")
      # self.view.insert(edit, self.view.sel()[0], ret )
      self.view.replace(edit, self.view.sel()[0], ret )
      #sublime.message_dialog(ret.decode("utf-8"))
    except subprocess.CalledProcessError as e:
      raise e
      #sublime.message_dialog(e.cmd)
      #sublime.message_dialog(e.output)
      #sublime.message_dialog(e)

登録する

コマンドに登録する。

[
{
  "Caption": "Edit",
  "id" : "edit",
  "children" : [
    { 
      "command": "paste_as_path",
      "mnemonic" : "" ,
      "caption": "Paste(Extend)"
     }
  ]
}
]

最後にショートカットを登録

     // ペースト
     { "keys": ["ctrl+v"], "command": "paste_as_path" },
     //dummy
     {}
]

コレで出来上がり。

f:id:takuya_1st:20150108201303j:plain

まぁ、これででも、通常ペーストより遅いんだ。 やっぱりサブプロセスの起動は遅いよねぇ かといって、コレ以上高速化する理由もないし。

SublimeTextにフルパスで貼り付けできるだけ良しとしよう。

正式なのは、SublimeTextの対応待ちにしよう。こだわって作ってもそのうちメインストリートで対応されるだろうし

参考資料

Macでファイルパスをクリップボードにコピーする方法@マカセル森本|Cocoaを飲む