« エディタからICompilationUnitオブジェクトを取得する方法 | トップページ | エディタを開く Part2 »

2004.08.27

テキストエディタ上のカーソル位置の取得方法

テキストエディタ上である機能が呼び出された時点でのカーソル(キャレット)位置の取得を行いたいときがある。カーソル位置から現在行を取得したり,カーソル位置がメソッド内かどうかを調べたり,などが例として考えられる。カーソル位置の取得なんて簡単にできそうな気がするが,実はそこには高い壁が存在している

AbstractTextEditorクラスを基底クラスとするテキストエディタは,ViewのコンポーネントとしてSWTのStyledTextクラスを利用している。このStyledTextクラスのgetCaretOffsetメソッドを呼び出すことで,カーソル位置を取得することができる。「なんだ,それならStyleTextオブジェクトを取得できればいいだけだから簡単じゃん」と思ってしまうが,ここで落とし穴が待っている。

StyledTextオブジェクトを取得するためには,ISourceViewerインタフェース(実際の定義は親のITextViewerインタフェース)のgetTextWidgetメソッドを使用すれば良いのだが,ISourceViewerオブジェクトを取得するためのAbstractTextEditorクラスのgetSourceViewerメソッドのアクセス修飾子がprotectedなのである。つまり,テキストエディタが持つStyledTextオブジェクトの取得は,AbstractTextEditorクラスのサブクラスからのみに限定されていることになる。これでは,Eclipse既存のテキストエディタ(TextEditorやJDTのCompilationUnitEditorなど)の外部(アクション代理オブジェクトなど)からはStyledTextオブジェクトを得ることができない。結果として,AbstractTextEditorクラスのサブクラスを作ってテキストエディタを自作しない限り,カーソル位置を取得することができないことになる。

もちろんAbstractTextEditorクラスのサブクラスを自作すれば,カーソル位置を取得することが可能になる。その方法は以下のようなコードになる。

  ISourceViewer sourceViewer = getSourceViewer();
  StyledText styledText = sourceViewer.getTextWidget();
  int widgetOffset = styledText.getCaretOffset();
  int modelOffset = widgetOffset2ModelOffset(sourceViewer, widgetOffset);

getSourceViewerメソッドを使ってISourceViewerオブジェクトを取得,その後ISourceViewerオブジェクトのgetTextWidgetメソッドを呼び出してStyledTextオブジェクトを取得する。そしてStyledTextオブジェクトのgetCaretOffsetメソッドを使って,カーソル位置を取得する。ただし,getCaretOffsetメソッドで取得したカーソル位置は,あくまで画面上の見かけの位置であり,テキスト全体から見た位置ではない。Eclipseのバージョン3から搭載されたFolding機能によってテキストのある部分が非表示になることがあるため,StyledTextオブジェクトのgetCaretOffsetメソッドの結果はFolding機能によって非表示になったテキストがすっ飛ばされた位置なのである。IDocumentインタフェースで表されるテキストのモデル(つまりテキスト全体)からみた現在のカーソル位置を得るために,widgetOffset2ModelOffsetメソッドが用意されている。widgetOffset2ModelOffsetメソッドにStyledTextオブジェクトから得たカーソル位置を渡すことにより,テキスト全体から見た位置を計算して返却してくれるので,その結果をカーソル位置として使用すればよい。

さて,上記の方法はもちろんAbstractTextEditorクラスのサブクラスからしか利用できない。では方法がないのかというとそんなことはなくて,テキストの選択情報を取得するための機構を流用することで(なんちゃってではあるが)カーソル位置の取得を実現できる。テキストが選択されていない状態で選択状況の取得を行うと,選択開始位置から長さ0の選択,という情報が得られるので,その選択開始位置をカーソル位置とすることができる。もちろんこの位置はテキスト全体から見たモデル上の位置である。

  IEditorPart editorPart = ...;

  ITextEditor textEditor = (ITextEditor)editorPart;
  ISelectionProvider selectionProvider = textEditor.getSelectionProvider();
  ISelection selection = selectionProvider.getSelection();
  ITextSelection textSelection = (ITextSelection)selection;
  int offset = textSelection.getOffset();

まず,エディタを表すIEditorPartオブジェクトをITextEditor型にキャストする。そしてITextEditorオブジェクトのgetSelectionProviderメソッドを使ってISelectionProviderオブジェクトを取得する。さらに,ISelectionProviderオブジェクトのgetSelectionメソッドを使用してISelectionオブジェクトを取得し,それをITextSelection型にキャストする。ITextSelectionオブジェクトには選択の開始位置および選択文字数が格納されているので,getOffsetメソッドを使って選択開始位置を取得する。結果として,この選択開始位置をカーソル位置とする。

ただし,この方法では完全にはカーソル位置を取得することができない。エディタ上で文字列の選択を[Shift]+[→]キーで行った場合,選択後のカーソルの位置は選択文字列の最後に位置する。つまり,カーソル位置は本当は「textSelection.getOffset() + textSelection.getLength()」なのだが,選択行為を[→]キーで行ったのか[←]キーで行ったのかを得る手段がないために,選択開始位置にカーソルがあるのか選択終了位置にカーソルがあるのか区別がつかない。よって,上記のように選択開始位置をカーソル位置とする,という固定的な方法にするしかないのである。

ワープロじゃなくてテキストエディタなんだから,カーソルの現在位置の取得メソッドくらい,publicで用意されていてもいい気がするけど,何か理由があるのだろうか。。。

|

« エディタからICompilationUnitオブジェクトを取得する方法 | トップページ | エディタを開く Part2 »

コメント

はじめまして、いつも参考にさせていただいております。2004年当時とは状況が違うのかもしれませんが、AbstractTextEditor の doGetSelection() が使えそうですね。

投稿: せいの | 2006.05.22 02:27 午後

コメントを書く



(ウェブ上には掲載しません)




トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/12631/1285941

この記事へのトラックバック一覧です: テキストエディタ上のカーソル位置の取得方法:

« エディタからICompilationUnitオブジェクトを取得する方法 | トップページ | エディタを開く Part2 »