Open Tools API その4:続・ソースエディタへのアクセス ソースエディタの「中身」をいじる
エディタの編集バッファ
ビュー(IOTAEditView)のBufferプロパティがソースエディタ内部のバッファで、ソースファイルの中身を編集する。インターフェースはITOAEditBuffer。
ソースエディタが1つのファイルをオープンしている場合、ビューは複数あっても、バッファは同一のを参照する。ソースエディタ(IOTASourceEditor)、ビュー(IOTAEditView)、バッファ(ITOAEditBuffer)の関係はこんな感じ。
┌ ビュー[0] ┐ ├ ビュー[1] ┤ ソースエディタ ┼ ビュー[2] ┼ バッファ │ … │ └ ビュー[n] ┘
バッファのEditBlockプロパティがエディタで選択されている範囲。以下は、エディタの選択部分の位置と内容を取得する例。
function GetCurrentSourceEditor: IOTASourceEditor; var i: Integer; Editor: IOTAEditor; ISourceEditor: IOTASourceEditor; CurrentModule: IOTAModule; begin Result := nil; CurrentModule := (BorlandIDEServices as IOTAModuleServices).CurrentModule; for i := 0 to CurrentModule.GetModuleFileCount - 1 do begin Editor := CurrentModule.GetModuleFileEditor(i); if Supports(Editor, IOTASourceEditor, ISourceEditor) then begin Result := ISourceEditor; Break; end; end; end procedure TfrmEditorStatus.Button2Click(Sender: TObject); var SourceEditor: IOTASourceEditor; EditBuffer: IOTAEditBuffer; Start, After: TOTACharPos; BlockType: TOTABlockType; begin SourceEditor := GetCurrentSourceEditor; // 現在アクティブなエディタを取得(GExpters由来) if SourceEditor = nil then Exit; Start := SourceEditor.BlockStart; // 選択範囲の開始位置 After := SourceEditor.BlockAfter; // 選択範囲の終了位置 // 現在編集中のファイルのバッファを取得 EditBuffer := (BorlandIDEServices as IOTAEditorServices).TopBuffer; if SourceEditor = nil then Exit; // 選択範囲の内容を取得 Memo1.Clear; Memo1.Text := EditBuffer.EditBlock.Text; end;
バッファへ直接アクセスする
セレクションではなく、バッファを直接アクセスする場合は、編集リーダー(IOTAEditReader)と編集ライター(IOTAEditWriter)を介してアクセスする。バッファはUTF-8で管理しているので、UnicodeStringからは明示的に変換してやらないと文字化けするので注意。
バッファの中身を読み取る手順は、エディタのCreateReaderメソッドで編集リーダーを生成し、GetTextメソッドで中身を取得する。
以下は、GExpertsのユーティリティ関数で、エディタのバッファからTStreamへ中身を読み込むもの。
// 編集リーダーからTStreamへ読み込む procedure GxSaveReaderToStream(EditReader: IOTAEditReader; Stream: TStream; TrailingNull: Boolean); const // Leave typed constant as is - needed for streaming code. TerminatingNullChar: AnsiChar = #0; var EditReaderPos: Integer; ReadDataSize: Integer; Buffer: array [0 .. EditReaderBufferSize] of AnsiChar; // Array of bytes, might be UTF-8 begin Assert(EditReader <> nil); Assert(Stream <> nil); EditReaderPos := 0; ReadDataSize := EditReader.GetText(EditReaderPos, Buffer, EditReaderBufferSize); Inc(EditReaderPos, ReadDataSize); while ReadDataSize = EditReaderBufferSize do begin Stream.Write(Buffer, ReadDataSize); ReadDataSize := EditReader.GetText(EditReaderPos, Buffer, EditReaderBufferSize); Inc(EditReaderPos, ReadDataSize); end; Stream.Write(Buffer, ReadDataSize); if TrailingNull then Stream.Write(TerminatingNullChar, SizeOf(TerminatingNullChar)); // The source parsers need this end; // ソースエディタの中身をTSTringsにロードする procedure GxLoadSourceEditorToUnicodeStrings(SourceEditor: IOTASourceEditor; Data: TStrings); var MemStream: TMemoryStream; begin Data.Clear; if not Assigned(SourceEditor) then raise Exception.Create ('No source editor in GxOtaLoadSourceEditorToUnicodeStrings'); // TODO: Check stream format for forms as text (Ansi with escaped unicode, or UTF-8) in Delphi 2007/2009 MemStream := TMemoryStream.Create; try GxSaveReaderToStream(SourceEditor.CreateReader, MemStream, False); MemStream.Position := 0; {$IFDEF UNICODE} Data.LoadFromStream(MemStream, TEncoding.UTF8); {$ELSE} if RunningDelphi8OrGreater then SynUnicode.LoadFromStream(Data, MemStream, seUTF8) else SynUnicode.LoadFromStream(Data, MemStream, seAnsi); {$ENDIF} finally FreeAndNil(MemStream); end; end;
どうも、CreateReaderで生成した編集リーダーは明示的に破棄する必要はなさそう。
一方、書き込む場合はIOTASourceEditorのCreateUndoableWriter*1メソッドで編集ライターを生成して、DeleteToメソッドで中身を削除したり、Insertメソッドで挿入する。
これまた、GExpertsのコードの一部。この関数は、エディタの中身を文字列で丸々入れ替える例。
procedure GxReplaceEditorText(SourceEditor: IOTASourceEditor; Text: string); var Writer: IOTAEditWriter; begin Assert(Assigned(SourceEditor)); Writer := SourceEditor.CreateUndoableWriter; if not Assigned(Writer) then raise Exception.Create('No edit writer'); Writer.DeleteTo(MaxLongint); Writer.Insert(PAnsiChar(AnsiToUtf8(Text))); Writer := nil; end;
文字列をAnsiToUtf8関数で明示的にUTF-8へ変換して、そのポインタを渡している。編集ライターも明示的に破棄する必要は無いみたい。