// created by momotaro
/*------------------------------------------------------------------------------
= ViViでテキストオブジェクトもどきを実装するスクリプト。
== 使い方。
run textobject command object-pattern

: command
i,id, c, y, <, > あたりを指定するよろし。
変なの指定して動作がおかしくなってもしらん。

: object-pattern
テキストオブジェクトのパターンを指定。

== テキストオブジェクトの種類。
a]とかabとかaBとかないけど、それはmapするときにパターンを増やしてくださいな。
iwはvvが使えるViViでは必要ないでしょう。一応mapで定義しときました。
asとapは、ごめんなさい。自分が使わないので作らないです。
日本語と相性悪そうだし。

: a[
"a [] block"。
[〜〜] で囲まれたブロックを表す。

: i[
"inner [] block"。
[〜〜] で囲まれたブロックの内側を表す。

: a(
"a block"。
(〜〜) で囲まれたブロックを表す。

: i(
"inner block"。
(〜〜) で囲まれたブロックの内側を表す。

: a<
"a <> block"。
<〜〜> で囲まれたブロックを表す。

: i<
"inner <> block"。
<〜〜> で囲まれたブロックの内側を表す。

: a{
"a Block"。
{〜〜} で囲まれたブロックを表す。

: i{
"inner Block"。
{〜〜} で囲まれたブロックの内側を表す。

: a"
"a string block"(って言葉でいいのかなぁ?)。
"〜〜" で囲まれたブロックを表す。

: i"
"inner string block"。
"〜〜" で囲まれたブロックの内側を表す。

: a'
"a string block"。
'〜〜' で囲まれたブロックを表す。

: i'
"inner string block"。
'〜〜' で囲まれたブロックの内側を表す。

: a,
"a argument block"。
ひとつ分の引数を表す。引数の区切り文字(,)を含む。
"hoge( aaa, bbb, ccc )"の場合、"aaa, "、"bbb, "、", ccc"がそれぞれa argument blockとなる。

: i,
"inner argument block"。
ひとつ分の引数を表す。引数の区切り文字(,)は含まない。
"hoge( aaa, bbb, ccc )"の場合、"aaa"、"bbb"、"ccc"がそれぞれinner argument blockとなる。

: a/
"a regexp block"。
/〜〜/ で囲まれたブロックを表す。

: i/
"inner regexp block"。
/〜〜/ で囲まれたブロックの内側を表す。

: ac
"a comment block"。
Line Comment、もしくはBlock Commentで囲まれたブロックを表す。

Line Commentの場合、コメント文字の手前の空白を含む。
また、コメントのみの行が複数続いた場合は、それらをまとめたブロックとして表す。
改行も選択範囲に含める。

Block Commentの場合、コメント終了文字の後にスペースしかない場合、改行も範囲に含める。

: ic
"inner comment block"。
Line Comment、もしくはBlock Commentで囲まれたブロックの内側を表す。
コメント文字の前後のスペースは範囲に含めない。

Line Commentの場合、ac とは異なり、常に一行分のコメントを表す。改行は含めない。

: ii
"inner indent block"。
現在行と同じまたはそれ以上のインデントレベルの行を表す。
空行はインデントレベル -1 として計算される。(必ずそこでblockが途切れる)

: ai
"a indent block"。
現在行と同じまたはそれ以上のインデントレベルの行と、その前後の行を表す。
空行はインデントレベル -1 として計算される。(必ずそこでblockが途切れる)

: iI
"inner large indent block"。
現在行と同じまたはそれ以上のインデントレベルの行を表す。
空行は無視して計算される。

: aI
"a large indent block"。
現在行と同じまたはそれ以上のインデントレベルの行と、その前後の行を表す。
空行は無視して計算される。


== VIMとの違い。
VIMってよく知らないんだけど、知る限りの違いを。

=== よいところ。
* argument blockをサポートしている。
* regexp blockをサポートしている。
* comment blockをサポートしている。
* indent blockをサポートしている。
* 全角の()「」等に対応している。

=== わるいところ。
* paragraph block, sentence block, word blockをサポートしていない。
* {}ブロックの解釈がVIMと若干異なる(ハズ)。
* exコマンドの履歴に :run textobject d i( とか出てきてしまう(T-T)
* ci(hoge<ESC> とかした後、一回でundoできない。
* 遅い(T-T)(T-T)
* blockが見つからないようなところで実行するとスタックオーバーフローする気がする。
* vを使った場合、ビジュアルモードは既に終了してしまっているので、
  その後さらにビジュアルモード用コマンドを使うことが出来ない。

== 注意。
バッファ用に $textobject_fFtT_match_list を使います。
この変数は上書きしないで下さいませ。

== マップするべし。
このスクリプトはごりごりマップを使わないとまともに使えないです。
下記のやつを.vivircに貼るとよさげ。

map ciw vvd
map diw vvc
map yiw vvy
map ca[ :run textobject c a[

map da[ :run textobject d a[

map ya[ :run textobject y a[

map va[ :run textobject v a[

map <a[ :run textobject < a[

map >a[ :run textobject > a[

map ci[ :run textobject c i[

map di[ :run textobject d i[

map yi[ :run textobject y i[

map vi[ :run textobject v i[

map <i[ :run textobject < i[

map >i[ :run textobject > i[

map ca] :run textobject c a[

map da] :run textobject d a[

map ya] :run textobject y a[

map va] :run textobject v a[

map <a] :run textobject < a[

map >a] :run textobject > a[

map ci] :run textobject c i[

map di] :run textobject d i[

map yi] :run textobject y i[

map vi] :run textobject v i[

map <i] :run textobject < i[

map >i] :run textobject > i[

map ca( :run textobject c a(

map da( :run textobject d a(

map ya( :run textobject y a(

map va( :run textobject v a(

map <a( :run textobject < a(

map >a( :run textobject > a(

map ci( :run textobject c i(

map di( :run textobject d i(

map yi( :run textobject y i(

map vi( :run textobject v i(

map <i( :run textobject < i(

map >i( :run textobject > i(

map ca) :run textobject c a(

map da) :run textobject d a(

map ya) :run textobject y a(

map va) :run textobject v a(

map <a) :run textobject < a(

map >a) :run textobject > a(

map ci) :run textobject c i(

map di) :run textobject d i(

map yi) :run textobject y i(

map vi) :run textobject v i(

map <i) :run textobject < i(

map >i) :run textobject > i(

map cab :run textobject c a(

map dab :run textobject d a(

map yab :run textobject y a(

map vab :run textobject v a(

map <ab :run textobject < a(

map >ab :run textobject > a(

map cib :run textobject c i(

map dib :run textobject d i(

map yib :run textobject y i(

map vib :run textobject v i(

map <ib :run textobject < i(

map >ib :run textobject > i(

map ca< :run textobject c a<

map da< :run textobject d a<

map ya< :run textobject y a<

map va< :run textobject v a<

map <a< :run textobject < a<

map >a< :run textobject > a<

map ci< :run textobject c i<

map di< :run textobject d i<

map yi< :run textobject y i<

map vi< :run textobject v i<

map <i< :run textobject < i<

map >i< :run textobject > i<

map ca> :run textobject c a<

map da> :run textobject d a<

map ya> :run textobject y a<

map va> :run textobject v a<

map <a> :run textobject < a<

map >a> :run textobject > a<

map ci> :run textobject c i<

map di> :run textobject d i<

map yi> :run textobject y i<

map vi> :run textobject v i<

map <i> :run textobject < i<

map >i> :run textobject > i<

map ca{ :run textobject c a{

map da{ :run textobject d a{

map ya{ :run textobject y a{

map va{ :run textobject v a{

map <a{ :run textobject < a{

map >a{ :run textobject > a{

map ci{ :run textobject c i{

map di{ :run textobject d i{

map yi{ :run textobject y i{

map vi{ :run textobject v i{

map <i{ :run textobject < i{

map >i{ :run textobject > i{

map ca} :run textobject c a{

map da} :run textobject d a{

map ya} :run textobject y a{

map va} :run textobject v a{

map <a} :run textobject < a{

map >a} :run textobject > a{

map ci} :run textobject c i{

map di} :run textobject d i{

map yi} :run textobject y i{

map vi} :run textobject v i{

map <i} :run textobject < i{

map >i} :run textobject > i{

map caB :run textobject c a{

map daB :run textobject d a{

map yaB :run textobject y a{

map vaB :run textobject v a{

map <aB :run textobject < a{

map >aB :run textobject > a{

map ciB :run textobject c i{

map diB :run textobject d i{

map yiB :run textobject y i{

map viB :run textobject v i{

map <iB :run textobject < i{

map >iB :run textobject > i{

map ca" :run textobject c a"

map da" :run textobject d a"

map ya" :run textobject y a"

map va" :run textobject v a"

map <a" :run textobject < a"

map >a" :run textobject > a"

map ci" :run textobject c i"

map di" :run textobject d i"

map yi" :run textobject y i"

map vi" :run textobject v i"

map <i" :run textobject < i"

map >i" :run textobject > i"

map ca' :run textobject c a'

map da' :run textobject d a'

map ya' :run textobject y a'

map va' :run textobject v a'

map <a' :run textobject < a'

map >a' :run textobject > a'

map ci' :run textobject c i'

map di' :run textobject d i'

map yi' :run textobject y i'

map vi' :run textobject v i'

map <i' :run textobject < i'

map >i' :run textobject > i'

map ca, :run textobject c a,

map da, :run textobject d a,

map ya, :run textobject y a,

map va, :run textobject v a,

map <a, :run textobject < a,

map >a, :run textobject > a,

map ci, :run textobject c i,

map di, :run textobject d i,

map yi, :run textobject y i,

map vi, :run textobject v i,

map <i, :run textobject < i,

map >i, :run textobject > i,

map ca/ :run textobject c a/

map da/ :run textobject d a/

map ya/ :run textobject y a/

map va/ :run textobject v a/

map <a/ :run textobject < a/

map >a/ :run textobject > a/

map ci/ :run textobject c i/

map di/ :run textobject d i/

map yi/ :run textobject y i/

map vi/ :run textobject v i/

map <i/ :run textobject < i/

map >i/ :run textobject > i/

map cac :run textobject c ac

map dac :run textobject d ac

map yac :run textobject y ac

map vac :run textobject v ac

map <ac :run textobject < ac

map >ac :run textobject > ac

map cic :run textobject c ic

map dic :run textobject d ic

map yic :run textobject y ic

map vic :run textobject v ic

map <ic :run textobject < ic

map >ic :run textobject > ic

map cai :run textobject c ai

map dai :run textobject d ai

map yai :run textobject y ai

map vai :run textobject v ai

map <ai :run textobject < ai

map >ai :run textobject > ai

map cii :run textobject c ii

map dii :run textobject d ii

map yii :run textobject y ii

map vii :run textobject v ii

map <ii :run textobject < ii

map >ii :run textobject > ii

map caI :run textobject c aI

map daI :run textobject d aI

map yaI :run textobject y aI

map vaI :run textobject v aI

map <aI :run textobject < aI

map >aI :run textobject > aI

map ciI :run textobject c iI

map diI :run textobject d iI

map yiI :run textobject y iI

map viI :run textobject v iI

map <iI :run textobject < iI

map >iI :run textobject > iI


*/

var setList = "[]<>(){}";

// 括弧のマッチ時の半角に対応。
function getfFtTHankakuTable()
{
	var matchTable;
	var i;

	for (i = 0; i < setList.length(); i++) {
		matchTable[setList.charAt(i)] = setList.charAt(i);
	}

	return matchTable;
}

//fFtTMatchTableを読み込んで設定を取得する
function getfFtTMatchTable(fname)
{
	var i;
	var matchTable;
	var buf;
	var findPos;

	var fp = stdioFile();
	if (!fp.open(fname)) {
		//読み込み失敗。半角のみ扱う
		matchTable = getfFtTHankakuTable();
	} else {
		//ファイルから読み込んで設定
		while (!fp.isEof()) {
			buf = fp.readString();
			if (buf =~ /^\/\//) continue;
			if (buf.length() == 0) continue;
			findPos = setList.find(buf.charAt(i));
			if (findPos >= 0) {
				buf = buf.split(" = ", 2)[1]; //=の後ろを取得
				buf = buf.replace("\\n", ""); //改行削除
				matchTable[setList.charAt(findPos)] = buf + setList.charAt(findPos);
			}
		}

		fp.close();
	}

	return matchTable;
}

//行コメント取得
function getLineComments()
{
	//設定取得
	var lineComments = thisView.getTypeSettings().getLineComment();
	for (var i = 0; i < lineComments.getCount(); i++) {
		lineComments[i] = lineComments[i].split(",", 3)[2];
	}
	return lineComments;
}

//ブロックコメント取得
function getBlockComments()
{
	var blockComments = thisView.getTypeSettings().getBlockComment();
	for (var i = 0; i < blockComments.getCount(); i++) {
		blockComments[i] = blockComments[i].split(",", 3)[2];
		blockComments[i] = blockComments[i].split(",", 2);
	}
	return blockComments;
}

//設定取得
function getConfig()
{
	var DEF_COMMAND			= "v";		//コマンドのデフォルト値
	var DEF_OBJECT_PATTERN	= "i\"";	//オブジェクトパターンのデフォルト値
	var config;

	//「Global Settings」→「vi」→「fFtT:全角文字にマッチ」
	config.fFtTMatchZenkaku = globalSettings.getBoolValue("fFtTMatchZenkaku");
	//「Global Settings」→「vi」→「fFtT マッチテーブルファイル」
	config.fFtTMatchTable = globalSettings.getTextValue("fFtTMatchTable");
	//「Type Settings」→「View」→「文字列中の \ をエスケープ文字とみなす」
	config.stringEscBackslash = thisView.getTypeSettings().getBoolValue("stringEscBackslash");
	//「Type Settings」→「View」→「シングルクォート文字列強調」
	config.snglQuoteStr = thisView.getTypeSettings().getBoolValue("snglQuoteStr");
	//「Type Settings」→「View」→「タブ幅」
	config.tab_length = thisView.getTypeSettings().getIntValue("tabWidth");
	//「Color Settings」→「Line Comment」
	config.lineComments = getLineComments();
	//「Color Settings」→「Block Comment」
	config.blockComments = getBlockComments();

	$config = config;

	// マッチテーブル作成
	if (config.fFtTMatchZenkaku) {
		if (!$textobject_fFtT_match_list) {
			$textobject_fFtT_match_list = getfFtTMatchTable(config.fFtTMatchTable);
		}
		config.fFtTMatchList = $textobject_fFtT_match_list;
	} else {
		config.fFtTMatchList = getfFtTHankakuTable();
	}

	//コマンド取得
	if (__argv.getCount() > 1) {
		config.command = __argv[1];
	} else {
		config.command = DEF_COMMAND;
	}

	//キャラクタ取得
	if (__argv.getCount() > 2) {
		config.objectPattern = __argv[2];
		if (config.objectPattern.length() != 2) {
			config.objectPattern = DEF_OBJECT_PATTERN;
		}
	} else {
		config.objectPattern = DEF_OBJECT_PATTERN;
	}
	config.isInner = (config.objectPattern.charAt(0) == "i");
	config.char = config.objectPattern.charAt(1);

	return config;
}

// pos位置より手前のcharsを探してそのposを返す
function findPrevPos(pos, chars)
{
	var flg = 0;
	var buf = thisView.getLineString(pos.line);
	var findPos;

	if (pos.offset >= 0) {
		buf = buf.left(pos.offset + 1);
	}

	var i;
	for (i = buf.length() - 1; i >= 0; i--) {
		findPos = chars.find(buf.charAt(i));
		if (findPos >= 0) {
			pos.char = chars.charAt(findPos);
			pos.offset = i;
			flg = 1;
			break;
		}
	}

	if (flg) {
		return pos;
	} else {
		pos.line--;
		if (pos.line == 0) {
			pos.line = -1;
			pos.offset = 0;
			return pos;
		} else {
			pos.offset = -1;
			return findPrevPos(pos, chars);
		}
	}
}

// pos位置より後ろのcharsを探してそのposを返す
function findNextPos(pos, chars)
{
	var flg = 0;
	var buf = thisView.getLineString(pos.line);
	var findPos;
	if (pos.offset >= 0) {
		buf = buf.mid(pos.offset);
	}

	var i;
	for (i = 0; i < buf.length(); i++) {
		findPos = chars.find(buf.charAt(i));
		if (findPos >= 0) {
			pos.char = chars.charAt(findPos);
			pos.offset += i;
			flg = 1;
			break;
		}
	}

	if (flg) {
		return pos;
	} else {
		pos.line++;
		pos.offset = 0;
		if (pos.line > thisView.getLineCount()) {
			pos.line = -1;
			return pos;
		} else {
			return findNextPos(pos, chars);
		}
	}
}

// pos位置にカーソルを移動する
function moveTo(pos)
{
	var col = thisView.offsetToColumn(pos.line, pos.offset);
	var nowPos = thisView.getCursorPos();

	if (pos.line == nowPos.line) {
		sendKeys(col + "|");
	} else {
		sendKeys(pos.line + "G" + col + "|");
	}
}

// 改行位置もしくはEOF位置かどうかを調べる
function isReturnPos(pos)
{
	return (pos.offset == thisView.getLineString(pos.line).length());
}

// posの比較
function comparePos(pos1, pos2)
{
	if (pos1.line < pos2.line) {
		return -1;
	} else if (pos1.line > pos2.line) {
		return 1;
	} else {
		if (pos1.offset < pos2.offset) {
			return -1;
		} else if (pos1.offset > pos2.offset) {
			return 1;
		} else {
			return 0;
		}
	}
}

// rangeの範囲を選択
function doSelect(range)
{
	if (range.isMember("line")) {
		//範囲じゃなくて位置が指定されていたら、そこに移動して終了
		moveTo(range);
		return 0;

	} else if (range.isMember("line1")) {
		//範囲が指定されていれば、それを選択する

		sendKeys("\x1b");
		//from位置に移動
		moveTo({line: range.line1, offset: range.offset1});
		//もし改行位置なら右移動
		if (isReturnPos({line: range.line1, offset: range.offset1})) {
			sendKeys("32772#c");
		}

		//選択
		sendKeys("v");
		moveTo({line: range.line2, offset: range.offset2});
		if (range.offset2 > 0 && isReturnPos({line: range.line2, offset: range.offset2})) {
			//もし改行位置なら右選択
			sendKeys("32793#c32793#c");

		} else if (!globalSettings.getBoolValue("vSelCharAtCursor")) {
			//もし「vモード:カーソル位置まで選択」がOFFなら右選択
			sendKeys("32793#c");

		}
		return 1;

	} else {
		//それ以外なら失敗
		return 0;
	}
}

// ポジションをひとつ進める
function incrPos(pos)
{
	pos.offset++;
	if (pos.offset >= thisView.getLineString(pos.line).length()) {
		pos.line++;
		pos.offset = 0;
	}
	return pos;
}

// ポジションをひとつ戻す
function decrPos(pos)
{
	pos.offset--;
	if (pos.offset < 0) {
		//一番最初ならそのまま返す
		if (pos.line == 1) {
			pos.offset = 0;
			return pos;
		}
		pos.line--;
		var offset = thisView.getLineString(pos.line).length();
		pos.offset = offset;
	}
	return pos;
}

// from〜toの範囲をrangeとして返す
function createRange(from, to)
{
	return {line1: from.line, offset1: from.offset, line2: to.line, offset2: to.offset};
}

// posの両端のクォートまでの範囲を取得する
function getQuoteRange(config, pos, quote)
{
	var i = 0;
	var ch;
	var line = thisView.getLineString(pos.line);
	var prevPos = {line: pos.line, offset: 0};
	var nextPos = {line: pos.line, offset: 0};
	var isInString = 0;

	while (i < line.length()) {
		ch = line.charAt(i);
		if (config.stringEscBackslash) {
			if (ch == "\\") {
				i++; //次は読み飛ばす。
			} else if (ch == quote) {
				isInString = !isInString;
				if (isInString) {
					if (i <= pos.offset) {
						prevPos.offset = i;
					} else {
						break;
					}
				} else {
					if (i >= pos.offset) {
						nextPos.offset = i;
						break;
					}
				}
			}
		} else {
			if (ch == quote) {
				if (isInString && line.charAt(i + 1) == quote) {
					i++; //次は読み飛ばす
				} else {
					isInString = !isInString;
					if (isInString) {
						if (i <= pos.offset) {
							prevPos.offset = i;
						} else {
							break;
						}
					} else {
						if (i >= pos.offset) {
							nextPos.offset = i;
							break;
						}
					}
				}
			}
		}

		i++;
	}

	//結果を返す
	if (nextPos.offset > 0) {
		if (config.isInner) {
			prevPos = incrPos(prevPos);
			nextPos = decrPos(nextPos);
		}
		return createRange(prevPos, nextPos);
	} else {
		return;
	}
}

// 現在位置が文字列内かどうか調べる
function isStringPos(config, pos)
{
	var quoteRange;

	quoteRange = getQuoteRange(config, pos, "\"");
	if (!(quoteRange == 0)) {
		return 1;
	}
	if (config.snglQuoteStr) {
		quoteRange = getQuoteRange(config, pos, "'");
		if (!(quoteRange == 0)) {
			return 1;
		}
	}
	return 0;
}

// LineComment開始位置を取得する
function getLineCommentPos(config, pos)
{
	//現在行を取得
	var line = thisView.getLineString(pos.line);
	var commentPos = {line: pos.line, offset: 0};

	var i = 0;
	var buf = line;
	var p;
	while (1) {
		p = buf.find(config.lineComments[i]);
		if (p >= 0) {
			commentPos.offset += p;
			if (!isStringPos(config, commentPos)) {
				//文字列内ではなかったのでコメントが見つかったこととする
				commentPos.char = config.lineComments[i];
				break;
			} else {
				//文字列内だったので次を探す
				commentPos.offset++;
				buf = buf.mid(commentPos.offset);
			}
		} else {
			//  コメントが見つからなかったので次のコメントを探す
			commentPos.offset = -1;
			i++;
			buf = line;
			if (i >= config.lineComments.getCount()) {
				commentPos.offset = -1;
				break;
			}
		}
	}

	if (commentPos.offset < 0 || commentPos.offset > pos.offset) {
		return;
	} else {
		return commentPos;
	}
}

// LineComment範囲を取得する
function getLineCommentRange(config, pos)
{
	var line = thisView.getLineString(pos.line);
	var prevPos = getLineCommentPos(config, pos);
	var nextPos = {line: pos.line, offset: line.length() - 1};

	//見つからなければ終わり
	if (prevPos == 0) return;

	var ch;
	if (config.isInner) {
		//inner comment block ならコメント部をはずす
		prevPos.offset += prevPos.char.length();
		//さらにスペースをはずす
		while (1) {
			ch = line.charAt(prevPos.offset);
			if (" \t".find(ch) < 0) break;
			if (prevPos.offset > nextPos.offset) break;
			prevPos.offset++;
		}
	} else {
		//a comment block なら手前のスペースを足す
		while (1) {
			prevPos.offset--;
			ch = line.charAt(prevPos.offset);
			if (prevPos.offset < 0 || " \t".find(ch) < 0) {
				prevPos.offset++;
				break;
			}
		}
		//コメントの手前が空白文字のみなら複数の行コメントが連続しているか調べる
		if (prevPos.offset == 0) {
			var commentPos;
			//先を調べる
			commentPos = prevPos;
			while (!(commentPos == 0) && commentPos.offset == 0) {
				nextPos = commentPos;
				commentPos.line++;
				if (commentPos.line > thisView.getLineCount()) break;
				commentPos.offset = thisView.getLineString(commentPos.line).length();
				commentPos = getLineCommentPos(config, commentPos);
			}
			nextPos.offset = thisView.getLineString(nextPos.line).length();

			//手前を調べる
			commentPos = prevPos;
			while (!(commentPos == 0) && commentPos.offset == 0) {
				prevPos = commentPos;
				commentPos.line--;
				if (commentPos.line <= 0) break;
				commentPos.offset = thisView.getLineString(commentPos.line).length();
				commentPos = getLineCommentPos(config, commentPos);
			}
		}
	}

	if (comparePos(prevPos, nextPos) > 0) {
		//範囲がなくなっちゃったら位置情報を返す
		return nextPos;
	} else {
		return createRange(prevPos, nextPos);
	}
}

// 指定行のインデントサイズを取得する
function getIndentSize(config, line)
{
	var result = 0;
	var str = thisView.getLineString(line);

	//空行なら -1 を返す
	var length = str.length();
	if (length == 0) return -1;

	for (var i = 0; i < length; i++) {
		var ch = str.charAt(i);
		if (ch == " ") {
			result += 1;
		} else if (ch == "\t") {
			result += config.tab_length;
		} else {
			break;
		}
	}

	return result;
}

// 同一インデント以上の範囲を取得する
function getIndentRange(config, pos, large_mode)
{
	var indentSize = getIndentSize(config, pos.line);
	var size = 0;

	//空行なら現在位置を返す
	if (indentSize < 0) {
		return pos;
	}
	//large_modeでインデントが無いなら現在位置を返す
	//(全行選択になっちゃうので)
	if (large_mode && indentSize == 0) {
		return pos;
	}

	//前方のインデントを調べていく
	var prevPos = {line: pos.line, offset: 0};
	while (prevPos.line > 0) {
		size = getIndentSize(config, prevPos.line - 1);
		if ((size < indentSize) && (!large_mode || (large_mode && size >= 0))) {
			break;
		}
		prevPos.line--;
	}

	//後方のインデントを調べていく
	var nextPos = {line: pos.line, offset: 0};
	var lineCount = thisView.getLineCount();
	while (nextPos.line <= lineCount) {
		size = getIndentSize(config, nextPos.line + 1);
		if ((size < indentSize) && (!large_mode || (large_mode && size >= 0))) {
			break;
		}
		nextPos.line++;
	}

	if (config.isInner) {
		//inner blockなら何もしない
	} else {
		//a blockなら前後の一行を含める
		if (prevPos.line > 1) prevPos.line--;
		if (nextPos.line < lineCount) nextPos.line++;
	}

	//最終行は行末までとする
	var offset = thisView.getLineString(nextPos.line).length();
	nextPos.offset = offset;

	return createRange(prevPos, nextPos);
}

// posの両端の括弧までの範囲を取得する
function getKakkoRange(config, pos, pre, post)
{
	var kakkoLevel = 1;

	//手前の括弧を探す
	var prevPos = findPrevPos(pos, pre + post);
	while (1) {
		if (prevPos.line < 0) return;
		if (!isStringPos(config, prevPos)) { //文字列内は無視
			if (pre.find(prevPos.char) >= 0) {
				kakkoLevel--;
			} else if (post.find(prevPos.char) >= 0) {
				kakkoLevel++;
			}
		}
		if (kakkoLevel) {
			//括弧レベルが無くならないうちは次の括弧を探す。
			prevPos = decrPos(prevPos);
			prevPos = findPrevPos(prevPos, pre+post);
		} else {
			break;
		}
	}

	//次の括弧を探す
	kakkoLevel = 1;
	var nextPos = findNextPos(incrPos(prevPos), pre+post);
	while (1) {
		if (nextPos.line < 0) return;
		if (!isStringPos(config, prevPos)) { //文字列内は無視
			if (pre.find(nextPos.char) >= 0) {
				kakkoLevel++;
			} else if (post.find(nextPos.char) >= 0) {
				kakkoLevel--;
			}
		}
		if (kakkoLevel) {
			//括弧レベルが無くならないうちは次の括弧を探す。
			nextPos.offset++;
			nextPos = findNextPos(nextPos, pre+post);
		} else {
			break;
		}
	}

	//innerの時の処理
	if (config.isInner) {
		//}の手前がスペースのみのときはそれは範囲に含めない。
		if (config.char == "{") {
			var buf = thisView.getLineString(nextPos.line).left(nextPos.offset);
			if (buf =~ /^\s*$/) {
				nextPos.offset = 0;
			}
		}
		prevPos = incrPos(prevPos);
		nextPos = decrPos(nextPos);
	}

	//結果を返す
	return createRange(prevPos, nextPos);
}

// pos位置にある引数の範囲を取得する
function getArgumentRange(config, pos)
{
	var kakkoLevel;
	var searchStr;
	var savePos;

	//手前の括弧かカンマを探す
	kakkoLevel = 1;
	searchStr = "(),";
	savePos = 0;
	var prevPos = findPrevPos(pos, searchStr);
	while (1) {
		if (prevPos.line < 0) return;
		if (!isStringPos(config, prevPos)) { //文字列内は無視
			if (prevPos.char == "(") {
				kakkoLevel--;
			} else if (prevPos.char == ")") {
				kakkoLevel++;
			} else if (prevPos.char == "," && kakkoLevel == 1) {
				//現在の括弧レベルで,が見つかったらsavePosに残しておき、
				//引数として有効かどうか、引き続き括弧を探す
				savePos = prevPos;
				searchStr = "()";
			}
		}
		if (kakkoLevel) {
			//括弧レベルが無くならないうちは前の括弧かカンマを探す。
			prevPos = findPrevPos(prevPos, searchStr);
		} else {
			break;
		}
	}
	if (savePos.char == ",") {
		prevPos = savePos;
	}

	//次の括弧かカンマを探す
	kakkoLevel = 1;
	searchStr = "(),";
	savePos = 0;
	var nextPos = findNextPos(pos, searchStr);
	while (1) {
		if (nextPos.line < 0) return;
		if (!isStringPos(config, nextPos)) { //文字列内は無視
			if (nextPos.char == "(") {
				kakkoLevel++;
			} else if (nextPos.char == ")") {
				kakkoLevel--;
			} else if (nextPos.char == "," && kakkoLevel == 1) {
				//現在の括弧レベルで,が見つかったらsavePosに残しておき、
				//引数として有効かどうか、引き続き括弧を探す
				savePos = nextPos;
				searchStr = "()";
			}
		}
		if (kakkoLevel) {
			//括弧レベルが無くならないうちは前の括弧かカンマを探す。
			nextPos.offset++;
			nextPos = findNextPos(nextPos, searchStr);
		} else {
			break;
		}
	}
	if (savePos.char == ",") {
		nextPos = savePos;
	}

	//括弧は取り除く
	if (prevPos.char == "(") {
		prevPos = incrPos(prevPos);
	}
	if (nextPos.char == ")") {
		nextPos = decrPos(nextPos);
	}
	//適宜カンマを取り除く
	if (config.isInner) {
		//inner blockなら、すべてのカンマを取り除く
		if (prevPos.char == ",") {
			prevPos = incrPos(prevPos);
		}
		if (nextPos.char == ",") {
			nextPos = decrPos(nextPos);
		}
	} else {
		//a blockで真ん中の引数なら、手前側の,を取り除く
		if (prevPos.char == nextPos.char) {
			prevPos = incrPos(prevPos);
		}
	}
	//スペースを調整する
	var ch;
	if (config.isInner) {
		//inner blockなら前後の空白を取り除く
		while (1) {
			ch = thisView.getLineString(prevPos.line).charAt(prevPos.offset);
			if (" \t\n".find(ch) < 0) break;
			prevPos = incrPos(prevPos);
			if (comparePos(prevPos, nextPos) >= 0) break;
		}
		while (1) {
			ch = thisView.getLineString(nextPos.line).charAt(nextPos.offset);
			if (" \t\n".find(ch) < 0) break;
			nextPos = decrPos(nextPos);
			if (comparePos(prevPos, nextPos) >= 0) break;
		}
	} else {
		//a blockなら場合によって分かれる
		if (prevPos.char == "(" && nextPos.char == ")") {
			//ひとつだけの引数の場合、何もしない

		} else if (nextPos.char == ")") {
			//最後の引数の場合、前の空白を足し、後ろの空白を消す
			while (1) {
				prevPos = decrPos(prevPos);
				ch = thisView.getLineString(prevPos.line).charAt(prevPos.offset);
				if (" \t\n".find(ch) < 0) {
					prevPos = incrPos(prevPos);
					break;
				}
			}
			while (1) {
				ch = thisView.getLineString(nextPos.line).charAt(nextPos.offset);
				if (" \t\n".find(ch) < 0) break;
				nextPos = decrPos(nextPos);
				if (comparePos(prevPos, nextPos) >= 0) break;
			}

		} else {
			//最初か真ん中の引数の場合、前の空白を消し、後ろの空白を足す
			while (1) {
				ch = thisView.getLineString(prevPos.line).charAt(prevPos.offset);
				if (" \t\n".find(ch) < 0) break;
				prevPos = incrPos(prevPos);
				if (comparePos(prevPos, nextPos) >= 0) break;
			}
			while (1) {
				nextPos = incrPos(nextPos);
				ch = thisView.getLineString(nextPos.line).charAt(nextPos.offset);
				if (" \t\n".find(ch) < 0) {
					nextPos = decrPos(nextPos);
					break;
				}
			}
		}
	}

	//結果を返す
	if (comparePos(prevPos, nextPos) <= 0) {
		return createRange(prevPos, nextPos);
	} else {
		return;
	}
}

// メイン
function main()
{
	//選択されている場合は何もしない
	if (thisView.isSelected()) {
		return;
	}

	var config = getConfig();

	//現在位置を取得
	var pos = thisView.getCursorPos();

	//コマンド適用範囲を取得して実行
	var range = 0;
	if (config.char == "\"" || config.char == "'") {
		range = getQuoteRange(config, pos, config.char);
	} else if (config.char == "/") {
		//常に「文字列中の \ をエスケープ文字とみなす」ようにする
		config.stringEscBackslash = 1;
		range = getQuoteRange(config, pos, config.char);
	} else if (config.char == "(") {
		range = getKakkoRange(config, pos, config.fFtTMatchList["("], config.fFtTMatchList[")"]);
	} else if (config.char == "[") {
		range = getKakkoRange(config, pos, config.fFtTMatchList["["], config.fFtTMatchList["]"]);
	} else if (config.char == "<") {
		range = getKakkoRange(config, pos, config.fFtTMatchList["<"], config.fFtTMatchList[">"]);
	} else if (config.char == "{") {
		range = getKakkoRange(config, pos, config.fFtTMatchList["{"], config.fFtTMatchList["}"]);
	} else if (config.char == ",") {
		range = getArgumentRange(config, pos);
	} else if (config.char == "c") {
		range = getLineCommentRange(config, pos);
	} else if (config.char == "i") {
		range = getIndentRange(config, pos, 0);
	} else if (config.char == "I") {
		range = getIndentRange(config, pos, 1);
	}

	//範囲選択
	if (doSelect(range)) {
		//コマンド実行
		if (config.command != "v") {
			sendKeys(config.command);
		}
	}

	//redoコマンドに追加
	var cmdStr = ":run";
	for(var i = 0; i < __argv.getCount(); ++i) {
		cmdStr += " ";
		cmdStr += __argv[i];
	}
	cmdStr += "\n";
	setRedoCommand( cmdStr );
}


/*------------------------------------------------------------------------------
= なんとなくバージョン管理。
== Ver.0.10(2007/11/14)
* indent blockに対応。

== Ver.0.09(2007/11/13)
* decrPos関数が正しく動かない場合がある問題を修正。
  (多分、ViViのバグだよなぁ。。。)

== Ver.0.08(2004/08/27)
* line comment blockに対応。
* c コマンドが正常に動かない問題を修正(ViVi#1904)。
* test_textobject.rbを作成。(一部の機能を)UnitTestするようにした。

== Ver.0.07(2004/08/25)
* {} block の時の挙動がおかしかったのを修正。
* 括弧系オブジェクトの開き括弧にカーソルがあるときにオブジェクトとみなされない問題を修正。
* \エスケープしないときの string block の判定がおかしかったのを修正。

== Ver.0.06(2004/08/23)
* /xxx/に対応。
* string blockの\エスケープの判定がおかしかったのを修正。
* .でredoできるようにした。

== Ver.0.05(2004/08/20)
* 「vモード:カーソル位置まで選択」の値をいじらないようにした。
* string blockの複数行サポートをやめ、"の中にあるかどうかを厳密に判断するようにした。
* argument blockを解釈するようにした。

== Ver.0.04(2004/08/09)
* 'xxx'に対応。

== Ver.0.03(2003/11/27)
* a block対応。
* mapにab, aBを追加。

== Ver.0.02(2003/11/27)
* fFtT:全角文字にマッチに対応。
* charが{の時は特別扱いし、閉じカッコの手前がスペースのみのときは範囲に含めないようにした。
* mapリストに ciw, diw, yiwを追加。

== Ver.0.01(2003/11/26)
* 文字列のエスケープに対応。
* 括弧のネストに対応。
* charに{を追加。

== Ver.0.00(2003/11/26)
* とりあえず作ってみて、IRCで公開。

= そしてTODO。
* fFtT全角対応の“”に対応していない。
  ちと特殊なんでどうしたものか…。
* ip, is, ap, asのサポート。
  やめやめ。
* function block対応。
* [リファクタリング] configをクラス化し、必要なときのみgetsettingするようにする。
------------------------------------------------------------------------------*/