ScreenNameCandidate?.js

概要

Statusを入力するときに@ほげほげを補完します。

@とたたくと補完リストの先頭が自動で入力されます。そのあとTabを入力するたびに次の候補へ移ります。
@のあと、idを入力していくと次々インクリメンタル検索されます。
スペースを押すと選択範囲が解除されて、末尾に半角スペースが入力されます。

補完リストはステータスを受信するたびに更新してます。自分のTLに流れた人しか補完できませんが、終了時に保存してるので次に起動したときに引き継がれるのでだいたい問題ないかなーと

こんなこともできますよーというあれなので高速化とかもっとまともな感じなのとかお待ちしています!

必要条件

Azurea 1.3.2(API Level >= 15)

スクリプト

// screen_nameのリスト
var candidates = [];
var candidates_hash = {};

// 前方一致で最初にマッチするインデックスを返す
Array.prototype.lowerBound = function(s){
	for(var i = 0; i < this.length; ++i){
		if(this[i].length >= s.length){
			if(this[i].slice(0, s.length) == s){
				return i;
			}
		}
	}
};

// 前方一致でマッチする項目を抽出する
Array.prototype.subset = function(s){
	var arr = [];
	var idx = this.lowerBound(s);
	for(var i = idx; i < this.length; ++i){
		if(this[i].length >= s.length){
			if(this[i].slice(0, s.length) == s){
				arr.push(this[i]);
			}else{
				break;
			}
		}else{
			break;
		}
	}
	return arr;
};

// 配列から検索する
Array.prototype.indexOf = function(s){
	for(var i = 0; i < this.length; ++i){
		if(this[i] == s) return i;
	}
	return -1;
};

// screen_name編集中なら@の位置をかえす。
// そうでないならundefined
function screenNameEditing(tex, beg)
{
	var hbeg;
	var hash = false;
	for(var i = beg - 1; i >= 0; --i){
		if(!tex.charAt(i).match(/[a-zA-Z0-9_]/)){
			if(tex.charAt(i) == '@'){
				hbeg = i;
				hash = true;
				break;
			}else{
				return undefined;
			}
		}
	}
	if(!hash) return undefined;
	return hbeg;
}

// screen_name補完を処理する
function processCandidate(fwd)
{
	var r = TextArea.selectedRange;
	var beg = (r & 0xffff);
	var hbeg = beg;
	var end = (r >> 16) & 0xffff;
	var tex = TextArea.text;
	if(arguments.length == 2){
		tex = tex.slice(0, beg) + arguments[1] + tex.slice(end);
		beg += 1;
		end = beg;
	}
	hbeg = screenNameEditing(tex, beg);
	if(hbeg == undefined) return false;
	var cds = candidates.subset(tex.slice(hbeg, beg));
	var idx = cds.lowerBound(tex.slice(hbeg, end));
	idx += fwd ? 1 : -1;
	if(idx == cds.length) idx = 0;
	if(idx < 0) idx = cds.length - 1;
	if(cds.length == 0 || cds[idx] == undefined) return arguments.length == 0;
	tex = tex.slice(0, hbeg) + cds[idx] + tex.slice(end);
	TextArea.text = tex;
	TextArea.select(beg, cds[idx].length + hbeg);
	return true;
}

// 文字入力されたときのイベント処理。
TextArea.addEventListener('char', function(c){
	if(c=='@'){
		// とりあえずscreen_nameを突っ込む
		var r = TextArea.selectedRange;
		var beg = (r & 0xffff);
		var hbeg = beg;
		var end = (r >> 16) & 0xffff;
		var cur = TextArea.cursor;
		var tex = TextArea.text;
		var partA = tex.slice(0, cur);
		var partB = tex.slice(cur);
		var candidate = false;
		if(beg != end && screenNameEditing(tex, beg) != undefined) return true;
		if(candidates.length > 0){
			tex = partA + candidates[0] + partB;
			candidate = true;
		}else{
			tex = partA + '@' + partB;
		}
		TextArea.text = tex;
		TextArea.cursor = cur + 1;
		if(candidate){
			TextArea.select(cur + 1, cur + candidates[0].length);
		}
		return true;
	}else if(c==' '){
		// 選択範囲があってかつ、screen_nameの範囲であれば選択を解除して半角スペースを入力
		var r = TextArea.selectedRange;
		var beg = (r & 0xffff);
		var hbeg = beg;
		var end = (r >> 16) & 0xffff;
		var tex = TextArea.text;
		if(beg == end) return false;
		hbeg = screenNameEditing(tex, beg);
		if(hbeg == undefined) return false;
		tex = tex.slice(0, end) + ' ' + tex.slice(end);
		TextArea.text = tex;
		TextArea.cursor = end + 1;
		return true;
	}else{
		// screen_nameとして有効な文字ならさらに補完
		if(c.match(/[a-zA-Z0-9_]/)){
			return processCandidate(true, c);
		}
	}
});

// KeyDownをフック
TextArea.addEventListener('keydown', function(c, m){
	// Tab: 次の候補を表示
	if(c==9){
		return processCandidate((m & 1) == 0);
	}
});

// ステータス前処理イベントをフック。
// 予測変換リストを更新する
TwitterService.addEventListener('preProcessTimelineStatus', function(status){
	var s = '@' + status.user.screen_name;
	if(candidates_hash[s] == undefined){
		candidates_hash[s] = 1;
		candidates.push(s);
		candidates.sort();
	}
});

// 終了イベントをフック
// 補完リストを保存する
System.addEventListener('quit', function(){
	var value = candidates.join('\n');
	if(value[0] == '\n') value = value.slice(1);
	FileSystem.privateStore.write('list.txt', value, 3);
});

// 保存された補完リストを読み込む
if(FileSystem.privateStore.exists('list.txt')){
	var value = FileSystem.privateStore.read('list.txt');
	if(value != ''){
		candidates = value.split('\n');
		candidates.sort();
		for(var i = 0; i < candidates.length; ++i) candidates_hash[candidates[i]] = 1;
	}
}

コメントとか

  • @より手前に文字が入力されている場合に補完がおかしくなるバグを修正 -- tmyt? 2011-04-17 (日) 01:00:25
  • 保存先をINIから専用のファイルに変更 -- tmyt? 2011-11-10 (木) 22:57:39

もし見えてたら空欄にしてね!:

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2016-08-27 (土) 10:33:39 (1443d)