root/lang/javascript/vimperator-plugins/branches/2.1/migemized_find.js

Revision 33602, 20.0 kB (checked in by snaka, 16 months ago)

PLUGIN_INFOのupdateURLを修正

Line 
1/* {{{
2Copyright (c) 2008, anekos.
3All rights reserved.
4
5Redistribution and use in source and binary forms, with or without modification,
6are permitted provided that the following conditions are met:
7
8    1. Redistributions of source code must retain the above copyright notice,
9       this list of conditions and the following disclaimer.
10    2. Redistributions in binary form must reproduce the above copyright notice,
11       this list of conditions and the following disclaimer in the documentation
12       and/or other materials provided with the distribution.
13    3. The names of the authors may not be used to endorse or promote products
14       derived from this software without specific prior written permission.
15
16THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
25THE POSSIBILITY OF SUCH DAMAGE.
26
27
28###################################################################################
29# http://sourceforge.jp/projects/opensource/wiki/licenses%2Fnew_BSD_license       #
30# に参考になる日本語訳がありますが、有効なのは上記英文となります。                #
31###################################################################################
32
33}}} */
34
35// PLUGIN_INFO {{{
36let PLUGIN_INFO =
37<VimperatorPlugin>
38  <name>Migemized Find</name>
39  <name lang="ja">Migemized Find</name>
40  <description>Migemize default page search.</description>
41  <description lang="ja">デフォルトのドキュメント内検索をミゲマイズする。</description>
42  <version>2.9.1</version>
43  <author mail="anekos@snca.net" homepage="http://d.hatena.ne.jp/nokturnalmortum/">anekos</author>
44  <license>new BSD License (Please read the source code comments of this plugin)</license>
45  <license lang="ja">修正BSDライセンス (ソースコードのコメントを参照してください)</license>
46  <updateURL>http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/migemized_find.js</updateURL>
47  <minVersion>2.0pre</minVersion>
48  <maxVersion>2.0pre</maxVersion>
49  <detail><![CDATA[
50    == Usage ==
51      検索ワードの一文字目が
52         '/'  => 正規表現検索
53         '?'  => Migemo検索
54         以外 => Migemo検索
55
56      検索ワードを指定色で強調表示する:
57        >||
58          :ml <検索ワード> [-c <色>]
59          :migelight <検索ワード> [-c <色>]
60        ||<
61
62      指定の色の強調表示を消す:
63        >||
64         :ml! <色1> <色2> ... <色N>
65         :migelight!  <色1> <色2> ... <色N>
66        ||<
67
68      全ての強調表示を消す:
69        >||
70          :ml! all
71          :migelight! all
72        ||<
73
74      ミ言語設定:
75        >||
76          let g:migemized_find_language = "cat";
77        ||<
78
79      インストール設定:
80        Vimperator の "/" などを置き換えるか否かの設定
81        false にすると、置き換えされない。
82        :migelight などのコマンドだけ使いたいときはこれを設定する
83        >||
84          let g:migemized_find_install = "false";
85        ||<
86
87    == Link ==
88        http://d.hatena.ne.jp/nokturnalmortum/20080805/1217941126
89  ]]></detail>
90</VimperatorPlugin>;
91// }}}
92
93
94(function () {
95
96  let do_install = s2b(liberator.globalVariables.migemized_find_install, true);
97
98  let XMigemoCore = Components.classes['@piro.sakura.ne.jp/xmigemo/factory;1']
99                     .getService(Components.interfaces.pIXMigemoFactory)
100                     .getService(liberator.globalVariables.migemized_find_language || 'ja');
101
102  let colors = {
103    white: '#ffffff',
104    whitesmoke: '#f5f5f5',
105    ghostwhite: '#f8f8ff',
106    aliceblue: '#f0f8ff',
107    lavendar: '#e6e6fa',
108    azure: '#f0ffff',
109    lightcyan: '#e0ffff',
110    mintcream: '#f5fffa',
111    honeydew: '#f0fff0',
112    ivory: '#fffff0',
113    beige: '#f5f5dc',
114    lightyellow: '#ffffe0',
115    lightgoldenrodyellow: '#fafad2',
116    lemonchiffon: '#fffacd',
117    floralwhite: '#fffaf0',
118    oldlace: '#fdf5e6',
119    cornsilk: '#fff8dc',
120    papayawhite: '#ffefd5',
121    blanchedalmond: '#ffebcd',
122    bisque: '#ffe4c4',
123    snow: '#fffafa',
124    linen: '#faf0e6',
125    antiquewhite: '#faebd7',
126    seashell: '#fff5ee',
127    lavenderblush: '#fff0f5',
128    mistyrose: '#ffe4e1',
129    gainsboro: '#dcdcdc',
130    lightgray: '#d3d3d3',
131    lightsteelblue: '#b0c4de',
132    lightblue: '#add8e6',
133    lightskyblue: '#87cefa',
134    powderblue: '#b0e0e6',
135    paleturquoise: '#afeeee',
136    skyblue: '#87ceeb',
137    mediumaquamarine: '#66cdaa',
138    aquamarine: '#7fffd4',
139    palegreen: '#98fb98',
140    lightgreen: '#90ee90',
141    khaki: '#f0e68c',
142    palegoldenrod: '#eee8aa',
143    moccasin: '#ffe4b5',
144    navajowhite: '#ffdead',
145    peachpuff: '#ffdab9',
146    wheat: '#f5deb3',
147    pink: '#ffc0cb',
148    lightpink: '#ffb6c1',
149    thistle: '#d8bfd8',
150    plum: '#dda0dd',
151    silver: '#c0c0c0',
152    darkgray: '#a9a9a9',
153    lightslategray: '#778899',
154    slategray: '#708090',
155    slateblue: '#6a5acd',
156    steelblue: '#4682b4',
157    mediumslateblue: '#7b68ee',
158    royalblue: '#4169e1',
159    blue: '#0000ff',
160    dodgerblue: '#1e90ff',
161    cornflowerblue: '#6495ed',
162    deepskyblue: '#00bfff',
163    cyan: '#00ffff',
164    aqua: '#00ffff',
165    turquoise: '#40e0d0',
166    mediumturquoise: '#48d1cc',
167    darkturquoise: '#00ced1',
168    lightseagreen: '#20b2aa',
169    mediumspringgreen: '#00fa9a',
170    springgreen: '#00ff7f',
171    lime: '#00ff00',
172    limegreen: '#32cd32',
173    yellowgreen: '#9acd32',
174    lawngreen: '#7cfc00',
175    chartreuse: '#7fff00',
176    greenyellow: '#adff2f',
177    yellow: '#ffff00',
178    gold: '#ffd700',
179    orange: '#ffa500',
180    darkorange: '#ff8c00',
181    goldenrod: '#daa520',
182    burlywood: '#deb887',
183    tan: '#d2b48c',
184    sandybrown: '#f4a460',
185    darksalmon: '#e9967a',
186    lightcoral: '#f08080',
187    salmon: '#fa8072',
188    lightsalmon: '#ffa07a',
189    coral: '#ff7f50',
190    tomato: '#ff6347',
191    orangered: '#ff4500',
192    red: '#ff0000',
193    deeppink: '#ff1493',
194    hotpink: '#ff69b4',
195    palevioletred: '#db7093',
196    violet: '#ee82ee',
197    orchid: '#da70d6',
198    magenta: '#ff00ff',
199    fuchsia: '#ff00ff',
200    mediumorchid: '#ba55d3',
201    darkorchid: '#9932cc',
202    darkviolet: '#9400d3',
203    blueviolet: '#8a2be2',
204    mediumpurple: '#9370db1',
205    gray: '#808080',
206    mediumblue: '#0000cd',
207    darkcyan: '#008b8b',
208    cadetblue: '#5f9ea0',
209    darkseagreen: '#8fbc8f',
210    mediumseagreen: '#3cb371',
211    teal: '#008080',
212    forestgreen: '#228b22',
213    seagreen: '#2e8b57',
214    darkkhaki: '#bdb76b',
215    peru: '#cd853f',
216    crimsin: '#dc143c',
217    indianred: '#cd5c5c',
218    rosybrown: '#bc8f8f',
219    mediumvioletred: '#c71585',
220    dimgray: '#696969',
221    black: '#000000',
222    midnightblue: '#191970',
223    darkslateblue: '#483d8b',
224    darkblue: '#00008b',
225    navy: '#000080',
226    darkslategray: '#2f4f4f',
227    green: '#008000',
228    darkgreen: '#006400',
229    darkolivegreen: '#556b2f',
230    olivedrab: '#6b8e23',
231    olive: '#808000',
232    darkgoldenrod: '#b8860b',
233    chocolate: '#d2691e',
234    sienna: '#a0522d',
235    saddlebrown: '#8b4513',
236    firebrick: '#b22222',
237    brown: '#a52a2a',
238    maroon: '#800000',
239    darkred: '#8b0000',
240    darkmagenta: '#8b008b',
241    purple: '#800080',
242    indigo: '#4b0082',
243  };
244
245  let colorsCompltions = [
246    [name, <span style={'color: ' + name}>{'\u25a0 ' + value}</span>]
247    for each ([name, value] in Iterator(colors))
248  ];
249
250  function s2b (s, d) (!/^(\d+|false)$/i.test(s)|parseInt(s)|!!d*2)&1<<!s;
251
252  function getPosition (elem) {
253    if (!elem)
254      return {x: 0, y: 0};
255    let parent = getPosition(elem.offsetParent);
256    return { x: (elem.offsetLeft || 0) + parent.x,
257             y: (elem.offsetTop  || 0) + parent.y  }
258  }
259
260  function slashArray (ary, center) {
261    let head = [], tail = [];
262    let current = head;
263    for (let i = 0; i < ary.length; i++) {
264      let it = ary[i];
265      if (it == center)
266        current = tail;
267      else
268        current.push(it);
269    }
270    return [head, tail];
271  }
272
273  let MF = {
274    // 定数
275    MODE_NORMAL: 0,
276    MODE_REGEXP: 1,
277    MODE_MIGEMO: 2,
278
279    // 全体で共有する変数
280    lastSearchText: null,
281    lastSearchExpr: null,
282    lastDirection: null,
283    lastColor: null,
284    currentSearchText: null,
285    currentSearchExpr: null,
286    currentColor: null,
287
288    // submit の為に使う
289    firstResult: null,
290
291    // --color-- の部分は置換される。
292    style: 'background-color: --color--; color: black; border: dotted 3px blue;',
293    findColor: 'lightblue',
294    highlightColor: 'orange',
295
296    // 手抜き用プロパティ
297    get document function () content.document,
298
299    // タブ毎に状態を保存するために、変数を用意
300    // 初回アクセス時に初期化を行う
301    get storage function () (
302      gBrowser.mCurrentTab.__migemized_find_storage ||
303      (gBrowser.mCurrentTab.__migemized_find_storage = {
304        highlightRemovers: {},
305      })
306    ),
307
308    // 現在のタブのフレームリスト
309    get currentFrames function () {
310      let result = [];
311      (function (frame) {
312        // ボディがない物は検索対象外なので外す
313        if (frame.document.body.localName.toLowerCase() == 'body')
314          result.push(frame);
315        for (let i = 0; i < frame.frames.length; i++)
316          arguments.callee(frame.frames[i]);
317      })(content);
318      return result;
319    },
320
321    // ボディを範囲とした Range を作る
322    makeBodyRange: function (frame) {
323      let range = frame.document.createRange();
324      range.selectNodeContents(frame.document.body);
325      return range;
326    },
327
328    // this.style に色を適用した物を返す
329    coloredStyle: function (color) {
330      return this.style.replace(/--color--/, color);
331    },
332
333    // 検索文字列から検索モードと検索文字列を得る。
334    searchTextToRegExpString: function (str) {
335      let [head, tail] = [str[0], str.slice(1)];
336      switch (head) {
337        case '/':
338          return tail;
339        case '?':
340          return XMigemoCore.getRegExp(tail);
341      }
342      return XMigemoCore.getRegExp(str);
343    },
344
345    // 指定色のハイライト削除
346    removeHighlight: function (color) {
347      (this.storage.highlightRemovers[color] || function () void(0))();
348      delete this.storage.highlightRemovers[color];
349    },
350
351    focusLink: function (range) {
352      let node = range.commonAncestorContainer;
353      while (node && node.parentNode) {
354        if (node.localName.toString().toLowerCase() == 'a')
355          return void(Components.lookupMethod(node, 'focus').call(node));
356        node = node.parentNode;
357      }
358    },
359
360    highlight: function (target, color, doScroll, setRemover) {
361      let span = this.document.createElement('span');
362
363      span.setAttribute('style', this.coloredStyle(color));
364      target.range.surroundContents(span);
365
366      if (doScroll) {
367        let scroll = function () {
368          let pos = getPosition(span);
369          target.frame.scroll(pos.x - (target.frame.innerWidth / 2),
370                              pos.y - (target.frame.innerHeight / 2));
371          let sel = target.frame.getSelection();
372          let r = target.range.cloneRange();
373          r.collapse(true);
374          sel.removeAllRanges();
375          sel.addRange(r);
376        };
377        setTimeout(scroll, 0);
378      }
379
380      let remover = function () {
381        let range = this.document.createRange();
382        range.selectNodeContents(span);
383        let content = range.extractContents();
384        range.setStartBefore(span);
385        range.insertNode(content);
386        range.selectNode(span);
387        range.deleteContents();
388      };
389
390      if (setRemover)
391        this.storage.highlightRemovers[color] = remover;
392
393      return remover;
394    },
395
396    find: function (str, backwards, range, start, end) {
397      if (!range)
398        range = this.makeBodyRange(this.currentFrames[0]);
399
400      if (!start) {
401        start = range.startContainer.ownerDocument.createRange();
402        start.setStartBefore(range.startContainer);
403      }
404      if (!end) {
405        end = range.endContainer.ownerDocument.createRange();
406        end.setEndAfter(range.endContainer);
407      }
408
409      // 検索方向に合わせて、開始終了位置を交換
410      if (backwards)
411        [start, end] = [end, start];
412
413      try {
414        return XMigemoCore.regExpFind(str, 'i', range, start, end, backwards);
415      } catch (e) {
416        return false;
417      }
418    },
419
420    findFirst: function (str, backwards, color) {
421      if (!color)
422        color = this.findColor;
423
424      this.lastDirection = backwards;
425      let expr = this.searchTextToRegExpString(str);
426      this.currentSearchText = str;
427      this.currentSearchExpr = expr;
428      this.currentColor = color;
429
430      let result, frames = this.currentFrames;
431      if (backwards)
432        frames = frames.reverse();
433
434      frames.some(function (frame)
435        let (ret = this.find(expr, backwards, this.makeBodyRange(frame)))
436          (ret && (result = this.storage.lastResult = { frame: frame, range: ret}))
437      , this);
438
439      this.removeHighlight(color);
440
441      if (result)
442        this.highlight(result, color, true, true);
443
444      this.firstResult = result;
445
446      return result;
447    },
448
449    findSubmit: function (str, backwards, color) {
450      this.findFirst(str, backwards, color);
451      return this.submit();
452    },
453
454    findAgain: function (reverse) {
455      let backwards = !!(!this.lastDirection ^ !reverse);
456      let last = this.storage.lastResult;
457      let frames = this.currentFrames;
458
459      // 前回の結果がない場合、(初め|最後)のフレームを対象にする
460      // findFirst と"似た"挙動になる
461      if (last) {
462        if (backwards) {
463          end = last.range.cloneRange();
464          end.setEnd(last.range.startContainer, last.range.startOffset);
465        } else {
466          start = last.range.cloneRange();
467          start.setStart(last.range.endContainer, last.range.endOffset);
468        }
469      } else {
470        let idx = backwards ? frames.length - 1
471                            : 0;
472        last = {frame: frames[0], range: this.makeBodyRange(frames[0])};
473      }
474
475      this.removeHighlight(this.lastColor);
476
477      let str = this.lastSearchExpr;
478      let start, end;
479
480      let result;
481      let ret = this.find(str, backwards, this.makeBodyRange(last.frame), start, end);
482
483      if (ret) {
484        result = {frame: last.frame, range: ret};
485      } else {
486        // 見つからなかったので、ほかのフレームから検索
487        let [head, tail] = slashArray(frames, last.frame);
488        let next = backwards ? head.reverse().concat(tail.reverse())
489                             : tail.concat(head);
490        next.some(function (frame)
491          let (ret = this.find(str, backwards, this.makeBodyRange(frame)))
492            (ret && (result = {frame: frame, range: ret}))
493        , this);
494      }
495
496      this.storage.lastResult = result;
497
498      if (result) {
499        this.highlight(result, this.lastColor, true, true);
500        this.focusLink(result);
501      }
502
503      return result;
504    },
505
506    submit: function () {
507      this.lastSearchText = this.currentSearchText;
508      this.lastSearchExpr = this.currentSearchExpr;
509      this.lastColor = this.currentColor;
510      if (this.firstResult)
511        this.focusLink(this.firstResult.range);
512      return this.firstResult;
513    },
514
515    cancel: function () {
516    },
517
518    highlightAll: function (str, color) {
519      let expr = this.searchTextToRegExpString(str);
520      this.lastSearchText = str;
521      this.lastSearchExpr = expr;
522
523      if (!color)
524        color = this.highlightColor;
525
526      this.removeHighlight(color);
527
528      let frames = this.currentFrames;
529      let removers = [];
530
531      frames.forEach(function (frame) {
532        let frameRange = this.makeBodyRange(frame);
533        let ret, start = frameRange;
534        while (ret = this.find(expr, false, frameRange, start)) {
535          removers.push(this.highlight({frame: frame, range: ret}, color, false, false));
536          start = ret.cloneRange();
537          start.setStart(ret.endContainer, ret.endOffset);
538        }
539      }, this);
540
541      this.storage.highlightRemovers[color] = function () { removers.forEach(function (it) it.call()); };
542
543      return removers;
544    },
545  };
546
547
548  // 前のタイマーを削除するために保存しておく
549  let delayCallTimer = null;
550  let delayedFunc = null;
551
552  // Vimp の仕様変更に対応
553  let _backwards;
554  let _findFirst = function (str, backwards) {
555      // 短時間に何回も検索をしないように遅延させる
556      delayedFunc = function () MF.findFirst(str, backwards);
557      if (delayCallTimer) {
558        delayCallTimer = null;
559        clearTimeout(delayCallTimer);
560      }
561      delayCallTimer = setTimeout(function () delayedFunc(), 500);
562  };
563
564  // ミゲモ化セット
565  let migemized = {
566    find: function find (str, backwards) {
567      _backwards = backwards;
568      if (str)
569        _findFirst(str, backwards);
570    },
571
572    findAgain: function findAgain (reverse) {
573      if (!MF.findAgain(reverse))
574        liberator.echoerr('not found: ' + MF.lastSearchText);
575    },
576
577    searchSubmitted: function searchSubmitted (command, forcedBackward) {
578      if (delayCallTimer) {
579        delayCallTimer = null;
580        clearTimeout(delayCallTimer);
581        delayedFunc();
582      }
583      if (!MF.submit())
584        liberator.echoerr('not found: ' + MF.currentSearchText);
585    },
586
587    searchCanceled: function searchCanceled () {
588      MF.cancel();
589    },
590
591    searchKeyPressed: function (str) {
592      _findFirst(str, _backwards);
593    },
594  };
595
596
597  // XXX for 2.1pre
598  // そのうち消す?
599  // http://vimperator.g.hatena.ne.jp/hogelog/20090511/1242060081
600  if (typeof search == "undefined") {
601    migemized.onSubmit = migemized.searchSubmitted;
602    migemized.onCancel = migemized.searchCanceled;
603    migemized.onKeyPress = migemized.searchKeyPressed;
604    search = finder;
605  }
606
607  // オリジナルの状態に戻せるように保存しておく
608  let (original = {}) {
609    for (let name in migemized)
610      original[name] = search[name];
611
612    function set (funcs) {
613      for (let name in funcs)
614        search[name] = funcs[name];
615    }
616
617    if (do_install)
618      set(migemized);
619
620    MF.install = function () set(migemized);
621    MF.uninstall = function () set(original);
622  }
623
624
625  // highlight コマンド
626  commands.addUserCommand(
627    ['ml', 'migelight'],
628    'Migelight matched words',
629    function (args) {
630      if (args.bang) {
631        let colors = args.join(' ') + ' ' + (args['-color'] || '');
632        liberator.execute('removemigelight ' + colors);
633      } else {
634        let r = MF.highlightAll(args.join(' '), args['-color']);
635        liberator.echo(r ? r.length + ' words migelighted.'
636                         : 'word not found.');
637      }
638    },
639    {
640      bang: true,
641      options: [
642        [['-color', '-c'], commands.OPTION_STRING, null, colorsCompltions],
643      ]
644    }
645  );
646
647  // remove highlight コマンド
648  commands.addUserCommand(
649    ['rml', 'removemigelight'],
650    'Remove migelight',
651    function (args) {
652      // HEAD (2)
653      if (args != undefined)
654        args = args.string;
655      if (!args)
656        return MF.removeHighlight(MF.highlightColor);
657      if (args == 'all')
658        return [f() for each (f in MF.storage.highlightRemovers)];
659      args.split(/\s+/).forEach(MF.removeHighlight, MF);
660    }
661  );
662
663  // find コマンド
664  commands.addUserCommand(
665    ['mf[ind]'],
666    'Migemized find',
667    function (args) {
668      if (!MF.findSubmit(args.join(' '), args['-backward'], args['-color']))
669        liberator.echoerr('not found: ' + MF.currentSearchText);
670    },
671    {
672      options: [
673        [['-backward', '-b'], commands.OPTION_NOARG],
674        [['-color', '-c'], commands.OPTION_STRING, null, colorsCompltions],
675      ]
676    }
677  );
678
679  // 外から使えるように
680  liberator.plugins.migemizedFind = MF;
681
682})();
Note: See TracBrowser for help on using the browser.