初マクロ
帰省して、やることがないのでマクロを作ってみた。
SVNのCommitマクロを作るつもりだったが、実家からはリポジトリにアクセスできないのでテスト不能。
ローカルにリポジトリを作るのも面倒なので休み明けかな。
作ったのはショートカットを開くマクロ。
jFD2はショートカットの作成・削除が簡単で良いのだが、開く時の操作性がDFに比べるといまいちなので、それを改善するのが目標。
機能:ショートカットを開く
(1) 起動した時にショートカットフォルダーにあるショートカットを一覧表示。
(2) その段階でインクリメンタルサーチ(Migemoが使えればMigemo)できる*1。
(3) Enterキーで選択したショートカットを開く。
(4) Shift+Enterキーで選択したショートカットを新しいタブで開く。
Swingでちょっとはまった。実家ではネット環境が貧弱なので調べるのも大変。
Groovyではインターフェイスの実装が楽でいいなぁ。KeyAdapterを使わなくてもKeyListenerの実装が簡単にできる。
import com.nullfish.lib.vfs.VFS import com.nullfish.lib.keymap.KeyStrokeMap import com.nullfish.app.jfd2.ui.container2.NumberedJFD2 import com.nullfish.app.jfd2.ui.container2.JFD2TitleUpdater import com.nullfish.app.jfd2.ui.container2.ContainerPosition import com.nullfish.app.jfd2.util.IncrementalSearcher import com.nullfish.app.jfd2.util.WildCardUtil import com.nullfish.app.jfd2.util.MigemoInfo import org.monazilla.migemo.Migemo import java.awt.event.KeyListener import java.awt.event.KeyEvent import java.awt.event.InputEvent import java.awt.event.KeyAdapter import javax.swing.KeyStroke files=[] shortcutDir=(String)jfd.getCommonConfigulation().getParam("shortcut_dir", "") new File(shortcutDir).list().each{ m=(it=~/(.+)\.jfdlnk$/) if(m.size()>0) files<<=m[0][1] } if(files.size()<1) return files=files.sort() dlg=new groovy.swing.SwingBuilder().dialog(title:"Open shortcut"){ box(axis:javax.swing.BoxLayout.Y_AXIS){ scrollPane(){ ls=list(listData:files,selectionMode:javax.swing.ListSelectionModel.SINGLE_SELECTION,selectedIndex:0) } panel(){ label(text:"Search:") word=label() } panel(){ label(text:"Enter:Open, Shift+Enter:NewTab, ESC:Cancel, Chars:Search") } } } dlg.pack() dlg.setLocationRelativeTo(null) dlg.setVisible(true) ls.addKeyListener([keyPressed:{e-> setIndex={pos-> ls.selectedIndex=pos ls.ensureIndexIsVisible(pos) ls.updateUI() } searchWord={ wd=MigemoInfo.usesMigemo()?Migemo.lookup(word.text):"^"+WildCardUtil.wildCard2Regex(word.text).toLowerCase() pattern=wd.bitwiseNegate() for(i in 0..files.size()-1){ if(files[i].toLowerCase()=~pattern){ setIndex(i) break } } } switch(KeyStrokeMap.getKeyStrokeForEvent(e)){ case KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0): case KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_DOWN_MASK): file=new File(new File(shortcutDir),files[ls.selectedIndex]+".jfdlnk") newPath=new String(file.readBytes()) newFile = VFS.getInstance(jfd).getFile(newPath) if(newFile != null){ if(e.modifiersEx&KeyEvent.SHIFT_DOWN_MASK){ newJFD = new NumberedJFD2() newJFD.init(jfd.jFDOwner.configDirectory) jfd.jFDOwner.addComponent(newJFD, ContainerPosition.MAIN_PANEL, new JFD2TitleUpdater(newJFD)) newJFD.model.setDirectoryAsynchIfNecessary(newFile, newFile.getParent(), newJFD) }else{ jfd.model.setDirectoryAsynchIfNecessary(newFile, newFile.parent, jfd) } } case KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0): dlg.dispose() break case KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0): case KeyStroke.getKeyStroke(KeyEvent.VK_H, InputEvent.CTRL_DOWN_MASK): switch(word.text.size()){ case 0:break case 1: word.text="" setIndex(0) break default: word.text=word.text.getAt(0..-2) searchWord() } break default: c=Character.toUpperCase(e.getKeyChar()).toString() if(word.text.size()<30&&IncrementalSearcher.ACCEPTABLE_CHARS.indexOf(c)>=0){ word.text+=(e.modifiersEx&KeyEvent.SHIFT_DOWN_MASK) ? c:e.getKeyChar().toString() searchWord() } } }] as KeyListener)
Groovyの実験
きっかけはjFD2のGroovyマクロで外部コマンドの実行方法を調べたこと。
当初はJavaでの方法をそのままGroovyに持ってきたのだが、ネットでみたサンプルに驚愕!
"ls -l".execute().in.eachLine{println it}
なんですかこれ?
すぐには理解できなかったが調べてみると、Stringクラスに"execute"メソッドを追加して、ProcessクラスにはInputStreamを取得できるプロパティ"in"を追加しているらしい。やりたい放題だなGroovy。
このコードを見て、気になったことを実験*1。
○実験その1
プロパティと書いたが、実際はgetIn()メソッドが追加されているだけらしい。
例えば既存のクラスで
sv=button.getVisible()
button.setVisible(true)
なんて書いていたのを
sv=button.visible
button.visible=true
なんて変えても、同じように動作する。
そこで疑問。
Javaのクラスが
public class Foo{ public String foo; private String bar; public Foo(){ foo="foo"; bar="bar"; } public String getFoo(){return bar;} }
だった時にGroovyで
foo=new Foo() println foo.getFoo() // ここは"bar"だよね println foo.foo // ここは?
とした時どうなるか?
・・結果。
"bar"でした。
"getXXX()"の代わりに"xXX"は安心して使えるということですね。
ちなみに上記のJavaクラスFooからgetFooメソッドを削除すると、ちゃんとpublic変数のfooを参照してくれます。
○実験その2
既存クラスへのメソッドの追加はどうやるか。
Rubyなら簡単だけどJavaじゃできないはず?Groovyでなぜできる?ってのは、このさい追求しないことにする。できるもんはできる。で、やってみた。
まずJavaクラスを用意
public class Foo{ private String foo; public Foo(){ foo="foo"; } }
んで、Groovy
Foo.metaClass.getFoo << { return foo; } foo=new Foo() println foo.getFoo() // foo.fooでも同じ
あっさりJavaのprivateフィールド読めてます。
privateメソッドもラップするメソッドを定義すれば問題なく呼べるでしょう。
それでいいのか?
Java的には問題あるかもしれんけど、まあ便利だからいいんじゃね。
*1:検証環境はGroovy 1.5.0
Subversion対応マクロ(update)
Subversionのファイル管理のために、わざわざEclipseを立ち上げるってのも何か間違ってる気がする。
ファイル管理はファイラーの役目、ってことでSubversion対応のマクロを少しずつ作ってみる。
まずはupdateから。
外部コマンドのsvnを呼んでるので、Windowsならsvn.exeにパスが通っている必要がある。
ダイアログを作るのにgroovyのSwingBuilderを使ったけど、これはいい!!
コンポーネントの関係が見やすいしレイアウトしやすい。
Swingを使うのは初めて(awtもほとんど忘れた)なので、希望のレイアウト(BoxLayout)を探すのにちょっと手間取ったけど。
import javax.swing.BoxLayout import com.nullfish.app.jfd2.ext_command.CommandExecuter dlg=new groovy.swing.SwingBuilder().dialog(title:"svn update ...doing"){ box(axis:BoxLayout.Y_AXIS){ scrollPane(){ ta=textArea(rows:15,columns:60) } btn=button(text:"OK",visible:false,actionPerformed:{dlg.dispose()}) } } dlg.pack() dlg.locationRelativeTo=null dlg.visible=true model=jfd.model files = model.markedFiles if(files == null || files.length == 0){ files = [model.selectedFile] } cmd=files.inject("svn update "){s,f->s+='"'+f.name+'" '} ta.append(cmd+"\n") CommandExecuter.instance .exec(cmd,CommandExecuter.USE_APP_SHELL,new File(model.currentDirectory.absolutePath)) .in.eachLine{ta.append(it+"\n")} dlg.title="svn update ...done" btn.visible=true btn.requestFocusInWindow()
マクロいろいろ
(1) 2chに張ったマクロ。
新しいタブを開く時にフォルダーにカーソルがあったら、そのフォルダーを開く。
DFのShift+Enterの動作に近い。
import com.nullfish.app.jfd2.ui.container2.NumberedJFD2 import com.nullfish.app.jfd2.ui.container2.JFD2TitleUpdater import com.nullfish.app.jfd2.ui.container2.ContainerPosition owner = jfd.getJFDOwner() if(owner==null) return model = jfd.getModel() selectedFile = model.getSelectedFile() newDir = selectedFile.isDirectory() ? selectedFile:model.getCurrentDirectory() newJFD = new NumberedJFD2() newJFD.init(owner.getConfigDirectory()) owner.addComponent(newJFD, ContainerPosition.MAIN_PANEL, new JFD2TitleUpdater(newJFD)) newJFD.getModel().setDirectoryAsynchIfNecessary(newDir, newDir.getParent(), newJFD)
(2) 長いから2chに張ってないマクロ。
1画面モードから2画面に縦分割。アクティブでないタブをサブウィンドウに移送。
コピー先を決めるのに便利。
import com.nullfish.app.jfd2.ui.container2.JFD2TitleUpdater import com.nullfish.app.jfd2.ui.container2.NumberedJFD2 import com.nullfish.app.jfd2.ui.container2.ContainerPosition owner=jfd.getJFDOwner() if(owner==null) return mainPanel=owner.getContentPane().getComponent(0) subPanel=owner.getContentPane().getComponent(1) tc0=mainPanel.getTabCount() tc1=subPanel.getTabCount() if(tc0+tc1<2) return owner.getContentPane().getLayout().setHorizontal(true) if(tc1==0){ // 1画面 colCount=(int)(jfd.getColumnCount()/2) if(colCount==0) colCount=1 for (ee in mainPanel.getComponents()) { e=ee.getComponent() e.setColumnCount(colCount) } } if(tc0==1){ // 次画面無し colCount=(int)(jfd.getColumnCount()*2) jfd.setColumnCount(colCount) for (ee in subPanel.getComponents()) { e=ee.getComponent() owner.removeComponent(e) e.setColumnCount(colCount) owner.addComponent(e, ContainerPosition.MAIN_PANEL,new JFD2TitleUpdater((NumberedJFD2)e)) } }else{ for (ee in mainPanel.getComponents()) { e=ee.getComponent() if(e.getComponent()==jfd) continue owner.removeComponent(e) owner.addComponent(e, ContainerPosition.SUB_PANEL,new JFD2TitleUpdater((NumberedJFD2)e)) break } } owner.removeComponent(jfd) owner.addComponent(jfd, ContainerPosition.MAIN_PANEL,new JFD2TitleUpdater((NumberedJFD2)jfd))
コードをpreタグで囲んでいたのに、確認したら括弧が重なった部分が、はてな記法の脚注と判断されてしまった・・。
preタグの中はそのまま出してくれ。
仕方が無くはてなのヘルプを参照。"スーパーpre記法"ってのを発見。コードの言語タイプを指定するとキーワードの色分けとかもしてくれる。言語にはgroovyも指定できるのは偉い"とかも見てくれる">*1。
ファイラー
長いことDFというファイラーを愛用してきた。
その機能に大きな不満もなかったけれど航海日誌でjFD2というファイラー(と、その2chスレ)を発見し、ここ数日試用している。
Java(Swing)アプリだが結構軽快に動いてくれる。まあこれはNetBeansを使ってるので驚かない。
DFでやっていたことはほとんどできそうだしマクロでカスタマイズもできるので、しばらく使い込んでみるつもり。
DFのキーアサインを体が覚えてしまっているので、jFD2のキーアサインをDF風に修正するかjFD2に慣れるか考え中。その昔はFDを長年使ってたので、すぐ思い出しそうな気もするが。
マクロの言語はGroovy。
JRubyのがいいなぁ、と当初は思ったのだが、実際にマクロを書いてみるとGroovyで良かったと思う。
Javaオブジェクトの操作がほとんどなのでRuby的な書き方で楽をできる局面が少ないし、Javaのソース*1から使える部分をパクってマクロにするのはJavaの文法に似ているGroovyの方が楽。
以下は、2chに張ったマクロ。
"home"という名前で作ったjFD2のショートカットに一発でジャンプする。
文字列リテラルは使わないでクラスで定義されている QuickAccessCommand.SHORTCUT_DIR などをつかうべき、とか、ショートカットファイルの構造に依存しているので、構造が変わってもいいように ShortCutFileクラスを使うべき、とか、突っ込みどころはあるけれどマクロは簡単に書いてなんぼなのでコレで良い。
import com.nullfish.lib.vfs.VFS;
shortCutDirPath = (String)jfd.getCommonConfigulation().getParam("shortcut_dir", "")
try{
file=new File(new File(shortCutDirPath),"home.jfdlnk")
homePath=new String(file.readBytes())
home = VFS.getInstance(jfd).getFile(homePath)
if(home != null){
jfd.getModel().setDirectoryAsynchIfNecessary(home, home.getParent(), jfd)
}
}catch(Exception e){
}
*1:jFD2はソースが公開されている
NetBeans6.0リリース
Netbeans6.0がリリースされました。
開発者・関係者の皆さんご苦労様でした。
Rubyの開発環境の選択肢が増えたことを素直に喜びたいと思います。
とりあえずRC1->RC2と引き継いだ設定ファイルをコピーして使っていますが快適です。
まあ、自分の使い方ではRCになってから特に問題なかったので当然ですが。
Rubyがサポートされた始めてのバージョンなので、今後の発展に期待します。
ということで、現バージョンについて細かい希望を数点。
(1) Projectsウィンドウでファイルを右クリックした時のコンテキストメニューにRunとDebugが欲しい。
プロジェクトでなくファイルを実行したい時に、いちいちファイルを開いて、そのコンテキストメニューから"Run File"を選んでました。
最近、Projectsウィンドウでも(Shift+F6)が効くことに気付いたけれど、メニューに合った方が判りやすい。
(2) 直前に行ったRunやDebugを簡単に繰り返せるようにして欲しい。
EclipseのRunボタン(直前のRunを再実行、ヒストリからも実行可能)のような仕組みがあると嬉しい。
(3) エディターでクラスの畳み込み表示を。
メソッドは畳み込んで表示できるけれど、クラスはできません。
Javaでは1クラス1ファイルなのであまり意味がないかもしれないが(でもインナークラスは畳み込みたいな)、Rubyでは複数クラスを書くこともあるし、クラスのテストコードを同じファイルに書く時なども畳み込めると便利。