aboutsummaryrefslogtreecommitdiffstats
path: root/migemized_find.js
diff options
context:
space:
mode:
authorretlet2008-11-05 06:04:44 +0000
committerretlet2008-11-05 06:04:44 +0000
commitc4489d32e21b8548976fec780eff4df613697a53 (patch)
tree1ecf88b6f2d562dde2f06e4824d7916f83fe4d8c /migemized_find.js
parent56cb882fc6f122c75608f8eb6142a607d3dc855f (diff)
downloadvimperator-plugins-c4489d32e21b8548976fec780eff4df613697a53.tar.bz2
手元の環境で動いているものをtags/2.0pre_1016からコピー
git-svn-id: http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/branches/1.2@22751 d0d07461-0603-4401-acd4-de1884942a52
Diffstat (limited to 'migemized_find.js')
-rw-r--r--migemized_find.js470
1 files changed, 470 insertions, 0 deletions
diff --git a/migemized_find.js b/migemized_find.js
new file mode 100644
index 0000000..5c5b461
--- /dev/null
+++ b/migemized_find.js
@@ -0,0 +1,470 @@
+// ==VimperatorPlugin==
+// @name Migemized Find
+// @description-ja デフォルトのドキュメント内検索をミゲマイズする。
+// @license Creative Commons 2.1 (Attribution + Share Alike)
+// @version 2.5
+// ==/VimperatorPlugin==
+//
+// Usage:
+// 検索ワードの一文字目が
+// '/' => 正規表現検索
+// '?' => Migemo検索
+// 以外 => Migemo検索
+//
+// :ml <検索ワード> [-c <色>]
+// :migelight <検索ワード> [-c <色>]
+// 検索ワードを指定色で強調表示する。
+//
+// :ml! <色1> <色2> ... <色N>
+// :migelight! <色1> <色2> ... <色N>
+// 指定の色の強調表示を消す
+//
+// :ml! all
+// :migelight! all
+// 全ての強調表示を消す。
+//
+// let g:migemized_find_language = "cat";
+// ミ言語設定
+//
+// Author:
+// anekos
+//
+// Link:
+// http://d.hatena.ne.jp/nokturnalmortum/20080805#1217941126
+
+(function () { try {
+
+ let XMigemoCore = Components.classes['@piro.sakura.ne.jp/xmigemo/factory;1']
+ .getService(Components.interfaces.pIXMigemoFactory)
+ .getService(liberator.globalVariables.migemized_find_language || 'ja');
+
+ function getPosition (elem) {
+ if (!elem)
+ return {x: 0, y: 0};
+ let parent = getPosition(elem.offsetParent);
+ return { x: (elem.offsetLeft || 0) + parent.x,
+ y: (elem.offsetTop || 0) + parent.y }
+ }
+
+ function slashArray (ary, center) {
+ let head = [], tail = [];
+ let current = head;
+ for (let i = 0; i < ary.length; i++) {
+ let it = ary[i];
+ if (it == center)
+ current = tail;
+ else
+ current.push(it);
+ }
+ return [head, tail];
+ }
+
+ let MF = {
+ // 定数
+ MODE_NORMAL: 0,
+ MODE_REGEXP: 1,
+ MODE_MIGEMO: 2,
+
+ // 全体で共有する変数
+ lastSearchText: null,
+ lastSearchExpr: null,
+ lastDirection: null,
+ lastColor: null,
+ currentSearchText: null,
+ currentSearchExpr: null,
+ currentColor: null,
+
+ // submit の為に使う
+ firstResult: null,
+
+ // --color-- の部分は置換される。
+ style: 'background-color: --color--; color: black; border: dotted 3px blue;',
+ findColor: 'lightblue',
+ highlightColor: 'orange',
+
+ // 手抜き用プロパティ
+ get document function () content.document,
+
+ // タブ毎に状態を保存するために、変数を用意
+ // 初回アクセス時に初期化を行う
+ get storage function () (
+ gBrowser.mCurrentTab.__migemized_find_storage ||
+ (gBrowser.mCurrentTab.__migemized_find_storage = {
+ highlightRemovers: {},
+ })
+ ),
+
+ // 現在のタブのフレームリスト
+ get currentFrames function () {
+ let result = [];
+ (function (frame) {
+ // ボディがない物は検索対象外なので外す
+ if (frame.document.body.localName.toLowerCase() == 'body')
+ result.push(frame);
+ for (let i = 0; i < frame.frames.length; i++)
+ arguments.callee(frame.frames[i]);
+ })(content);
+ return result;
+ },
+
+ // ボディを範囲とした Range を作る
+ makeBodyRange: function (frame) {
+ let range = frame.document.createRange();
+ range.selectNodeContents(frame.document.body);
+ return range;
+ },
+
+ // this.style に色を適用した物を返す
+ coloredStyle: function (color) {
+ return this.style.replace(/--color--/, color);
+ },
+
+ // 検索文字列から検索モードと検索文字列を得る。
+ searchTextToRegExpString: function (str) {
+ let [head, tail] = [str[0], str.slice(1)];
+ switch (head) {
+ case '/':
+ return tail;
+ case '?':
+ return XMigemoCore.getRegExp(tail);
+ }
+ return XMigemoCore.getRegExp(str);
+ },
+
+ // 指定色のハイライト削除
+ removeHighlight: function (color) {
+ (this.storage.highlightRemovers[color] || function () void(0))();
+ delete this.storage.highlightRemovers[color];
+ },
+
+ focusLink: function (range) {
+ let node = range.commonAncestorContainer;
+ while (node && node.parentNode) {
+ if (node.localName.toString().toLowerCase() == 'a')
+ return void(Components.lookupMethod(node, 'focus').call(node));
+ node = node.parentNode;
+ }
+ },
+
+ highlight: function (target, color, doScroll, setRemover) {
+ let span = this.document.createElement('span');
+
+ span.setAttribute('style', this.coloredStyle(color));
+ target.range.surroundContents(span);
+
+ if (doScroll) {
+ let scroll = function () {
+ let pos = getPosition(span);
+ target.frame.scroll(pos.x - (target.frame.innerWidth / 2),
+ pos.y - (target.frame.innerHeight / 2));
+ let sel = target.frame.getSelection();
+ let r = target.range.cloneRange();
+ r.collapse(true);
+ sel.removeAllRanges();
+ sel.addRange(r);
+ };
+ setTimeout(scroll, 0);
+ }
+
+ let remover = function () {
+ let range = this.document.createRange();
+ range.selectNodeContents(span);
+ let content = range.extractContents();
+ range.setStartBefore(span);
+ range.insertNode(content);
+ range.selectNode(span);
+ range.deleteContents();
+ };
+
+ if (setRemover)
+ this.storage.highlightRemovers[color] = remover;
+
+ return remover;
+ },
+
+ find: function (str, backwards, range, start, end) {
+ if (!range)
+ range = this.makeBodyRange(this.currentFrames[0]);
+
+ if (!start) {
+ start = range.startContainer.ownerDocument.createRange();
+ start.setStartBefore(range.startContainer);
+ }
+ if (!end) {
+ end = range.endContainer.ownerDocument.createRange();
+ end.setEndAfter(range.endContainer);
+ }
+
+ // 検索方向に合わせて、開始終了位置を交換
+ if (backwards)
+ [start, end] = [end, start];
+
+ try {
+ return XMigemoCore.regExpFind(str, 'i', range, start, end, backwards);
+ } catch (e) {
+ return false;
+ }
+ },
+
+ findFirst: function (str, backwards, color) {
+ if (!color)
+ color = this.findColor;
+
+ this.lastDirection = backwards;
+ let expr = this.searchTextToRegExpString(str);
+ this.currentSearchText = str;
+ this.currentSearchExpr = expr;
+ this.currentColor = color;
+
+ let result, frames = this.currentFrames;
+ if (backwards)
+ frames = frames.reverse();
+
+ for each (let frame in frames) {
+ let ret = this.find(expr, backwards, this.makeBodyRange(frame));
+ if (ret) {
+ result = this.storage.lastResult = {
+ frame: frame,
+ range: ret,
+ };
+ break;
+ }
+ }
+
+ this.removeHighlight(color);
+
+ if (result)
+ this.highlight(result, color, true, true);
+
+ this.firstResult = result;
+
+ return result;
+ },
+
+ findSubmit: function (str, backwards, color) {
+ this.findFirst(str, backwards, color);
+ return this.submit();
+ },
+
+ findAgain: function (reverse) {
+ let backwards = !!(!this.lastDirection ^ !reverse);
+ let last = this.storage.lastResult;
+ let frames = this.currentFrames;
+
+ // 前回の結果がない場合、(初め|最後)のフレームを対象にする
+ // findFirst と"似た"挙動になる
+ if (last) {
+ if (backwards) {
+ end = last.range.cloneRange();
+ end.setEnd(last.range.startContainer, last.range.startOffset);
+ } else {
+ start = last.range.cloneRange();
+ start.setStart(last.range.endContainer, last.range.endOffset);
+ }
+ } else {
+ let idx = backwards ? frames.length - 1
+ : 0;
+ last = {frame: frames[0], range: this.makeBodyRange(frames[0])};
+ }
+
+ this.removeHighlight(this.lastColor);
+
+ let str = this.lastSearchExpr;
+ let start, end;
+
+ let result;
+ let ret = this.find(str, backwards, this.makeBodyRange(last.frame), start, end);
+
+ if (ret) {
+ result = {frame: last.frame, range: ret};
+ } else {
+ // 見つからなかったので、ほかのフレームから検索
+ let [head, tail] = slashArray(frames, last.frame);
+ let next = backwards ? head.reverse().concat(tail.reverse())
+ : tail.concat(head);
+ for each (let frame in next) {
+ let r = this.find(str, backwards, this.makeBodyRange(frame));
+ if (r) {
+ result = {frame: frame, range: r};
+ break;
+ }
+ }
+ }
+
+ this.storage.lastResult = result;
+
+ if (result) {
+ this.highlight(result, this.lastColor, true, true);
+ this.focusLink(result);
+ }
+
+ return result;
+ },
+
+ submit: function () {
+ this.lastSearchText = this.currentSearchText;
+ this.lastSearchExpr = this.currentSearchExpr;
+ this.lastColor = this.currentColor;
+ if (this.firstResult)
+ this.focusLink(this.firstResult.range);
+ return this.firstResult;
+ },
+
+ cancel: function () {
+ },
+
+ highlightAll: function (str, color) {
+ let expr = this.searchTextToRegExpString(str);
+ this.lastSearchText = str;
+ this.lastSearchExpr = expr;
+
+ if (!color)
+ color = this.highlightColor;
+
+ this.removeHighlight(color);
+
+ let frames = this.currentFrames;
+ let removers = [];
+
+ for each (let frame in frames) {
+ let frameRange = this.makeBodyRange(frame);
+ let ret, start = frameRange;
+ while (ret = this.find(expr, false, frameRange, start)) {
+ removers.push(this.highlight({frame: frame, range: ret}, color, false, false));
+ start = ret.cloneRange();
+ start.setStart(ret.endContainer, ret.endOffset);
+ }
+ }
+
+ this.storage.highlightRemovers[color] = function () { removers.forEach(function (it) it.call()); };
+
+ return removers;
+ },
+ };
+
+
+ // 前のタイマーを削除するために保存しておく
+ let delayCallTimer = null;
+ let delayedFunc = null;
+
+ // Vimp の仕様変更に対応
+ let _backwards;
+ let _findFirst = function (str, backwards) {
+ // 短時間に何回も検索をしないように遅延させる
+ delayedFunc = function () MF.findFirst(str, backwards);
+ if (delayCallTimer) {
+ delayCallTimer = null;
+ clearTimeout(delayCallTimer);
+ }
+ delayCallTimer = setTimeout(function () delayedFunc(), 500);
+ };
+
+ // ミゲモ化セット
+ let migemized = {
+ find: function find (str, backwards) {
+ _backwards = backwards;
+ if (str)
+ _findFirst(str, backwards);
+ },
+
+ findAgain: function findAgain (reverse) {
+ if (!MF.findAgain(reverse))
+ liberator.echoerr('not found: ' + MF.lastSearchText);
+ },
+
+ searchSubmitted: function searchSubmitted (command, forcedBackward) {
+ if (delayCallTimer) {
+ delayCallTimer = null;
+ clearTimeout(delayCallTimer);
+ delayedFunc();
+ }
+ if (!MF.submit())
+ liberator.echoerr('not found: ' + MF.currentSearchText);
+ },
+
+ searchCanceled: function searchCanceled () {
+ MF.cancel();
+ },
+
+ searchKeyPressed: function (str) {
+ _findFirst(str, _backwards);
+ },
+ };
+
+
+ // オリジナルの状態に戻せるように保存しておく
+ let (original = {}) {
+ for (let name in migemized)
+ original[name] = search[name];
+
+ function set (funcs) {
+ for (let name in funcs)
+ search[name] = funcs[name];
+ }
+
+ set(migemized);
+
+ MF.install = function () set(migemized);
+ MF.uninstall = function () set(original);
+ }
+
+
+ // highlight コマンド
+ commands.addUserCommand(
+ ['ml', 'migelight'],
+ 'Migelight matched words',
+ function (opts, bang) {
+ if (bang) {
+ let colors = opts.arguments.join(' ') + ' ' + (opts['-color'] || '');
+ liberator.execute('removemigelight ' + colors);
+ } else {
+ let r = MF.highlightAll(opts.arguments.join(' '), opts['-color']);
+ liberator.echo(r ? r.length + ' words migelighted.'
+ : 'word not found.');
+ }
+ },
+ {
+ bang: true,
+ options: [
+ [['-color', '-c'], commands.OPTION_STRING],
+ ]
+ }
+ );
+
+ // remove highlight コマンド
+ commands.addUserCommand(
+ ['rml', 'removemigelight'],
+ 'Remove migelight',
+ function (args) {
+ // HEAD (2)
+ if (args != undefined)
+ args = args.string;
+ if (!args)
+ return MF.removeHighlight(MF.highlightColor);
+ if (args == 'all')
+ return [f() for each (f in MF.storage.highlightRemovers)];
+ for each (let color in args.split(/\s+/))
+ MF.removeHighlight(color);
+ }
+ );
+
+ // find コマンド
+ commands.addUserCommand(
+ ['mf[ind]'],
+ 'Migemized find',
+ function (opts) {
+ if (!MF.findSubmit(opts.arguments.join(' '), opts['-backward'], opts['-color']))
+ liberator.echoerr('not found: ' + MF.currentSearchText);
+ },
+ {
+ options: [
+ [['-backward', '-b'], commands.OPTION_NOARG],
+ [['-color', '-c'], commands.OPTION_STRING],
+ ]
+ }
+ );
+
+ // 外から使えるように
+ liberator.plugins.migemizedFind = MF;
+
+}catch(e){liberator.log(e);}})();