root/lang/javascript/vimperator-plugins/branches/2.2/auto_word_select_mode.js

Revision 34289, 11.7 kB (checked in by snaka, 14 months ago)

Removed some debug code.

Line 
1//
2// auto_word_select_mode.js
3//
4// LICENSE: {{{
5//
6//     This software distributable under the terms of an MIT-style license.
7//
8//     Copyright (c) 2009 snaka<snaka.gml@gmail.com>
9//
10//     Permission is hereby granted, free of charge, to any person obtaining a copy
11//     of this software and associated documentation files (the "Software"), to deal
12//     in the Software without restriction, including without limitation the rights
13//     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14//     copies of the Software, and to permit persons to whom the Software is
15//     furnished to do so, subject to the following conditions:
16//
17//     The above copyright notice and this permission notice shall be included in
18//     all copies or substantial portions of the Software.
19//
20//     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21//     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22//     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23//     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24//     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25//     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26//     THE SOFTWARE.
27//
28//     OSI page : http://opensource.org/licenses/mit-license.php
29//     Japanese : http://sourceforge.jp/projects/opensource/wiki/licenses%2FMIT_license
30//
31// }}}
32
33// PLUGIN INFO: {{{
34var PLUGIN_INFO =
35<VimperatorPlugin>
36  <name>{NAME}</name>
37  <description>Add auto word select mode.</description>
38  <description lang="ja">単語を自動選択するモードを追加します</description>
39  <minVersion>2.0</minVersion>
40  <maxVersion>2.2pre</maxVersion>
41  <updateURL>http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/auto_word_select_mode.js</updateURL>
42  <author mail="snaka.gml@gmail.com" homepage="http://vimperator.g.hatena.ne.jp/snaka72/">snaka</author>
43  <license>MIT style license</license>
44  <version>1.2.3</version>
45  <detail><![CDATA[
46    == Subject ==
47    Add auto word select mode.
48    Mode name is "AUTO_WORD_SELECT".
49    Press 'I' key to entering to AUTO_WORD_SELECT mode.
50    If you want exit mode, press 'I' key again.
51    This mode alway selects current word.
52
53    == Global variables ==
54    g:auto_word_select_key:
55      The key that entering to AUTO_WORD_SELECT mode.
56      Default is 'I'.
57
58    == About define keymap with AUTO_WORD_SELECT mode ==
59    The example of defining the key-map for the AUTO_WORD_SELECT mode
60    is shown as follows.
61
62    The following definitions are examples of assign behavior that
63    displays the translation result of the word selecting it by 'alc'
64    service by using multi_requester.js for 's' key.
65
66    >||
67    liberator.registerObserver("enter", function() {
68      mappings.addUserMap(
69        [modes.AUTO_WORD_SELECT],
70        ["s"],
71        "Translate selected word by multi_requester.js.",
72        function() {
73          // FIXME:
74          // A present mode is preserved in the stack beforehand by the push() method
75          // because it doesn't return to AUTO_WORD_SELECT mode before that when
76          // returning from the OUTPUT_MULTILINE mode.
77          modes.push(modes.AUTO_WORD_SELECT, null, true);
78
79          var selText = content.getSelection().toString();
80          var pattern = /[a-zA-Z]+/;
81          selText = pattern.test(selText) ? pattern.exec(selText) : selText;
82          events.feedkeys(":mr alc " + selText + "<CR>", true, true);
83        }
84      );
85    });
86    ||<
87
88  ]]></detail>
89  <detail lang="ja"><![CDATA[
90    == 概要 ==
91    単語を自動選択するモード(AUTO_WORD_SELECT)を追加します。
92    'I'キーを押すことによって、AUTO_WORD_SELECTモードに移行します。
93    このモードを抜けるには、再度'I'キーを押します。
94    このモードでは常に単語が選択されている状態になり、キャレットの
95    移動の単位も単語毎の移動になります。
96    コンテンツ内の単語を頻繁に選択&検索する場合などに便利です。
97
98    == グローバル変数 ==
99    g:auto_word_select_key:
100      AUTO_WORD_SELECTモードに移行するためのキーです。
101      デフォルトは'I'です。
102
103    == AUTO_WORD_SELECTモード用のマップの定義について ==
104    このモード用のマップの定義例として、multi_requester.jsを使用して
105    web上の辞書サービスを使用して検索結果を表示するためのマップを
106    定義する例を示します。
107
108    >||
109    liberator.registerObserver("enter", function() {
110      mappings.addUserMap(
111        [modes.AUTO_WORD_SELECT],
112        ["s"],
113        "Translate selected word by multi_requester.js.",
114        function() {
115          // FIXME:
116          // A present mode is preserved in the stack beforehand by the push() method
117          // because it doesn't return to AUTO_WORD_SELECT mode before that when
118          // returning from the OUTPUT_MULTILINE mode.
119          modes.push(modes.AUTO_WORD_SELECT, null, true);
120
121          var selText = content.getSelection().toString();
122          var pattern = /[a-zA-Z]+/;
123          selText = pattern.test(selText) ? pattern.exec(selText) : selText;
124          events.feedkeys(":mr alc " + selText + "<CR>", true, true);
125        }
126      );
127    });
128    ||<
129
130    上記の例ではAUTO_WORD_SELECTモードにおいて、単語を選択した後、's'キーを
131    押すと'alc'で登録されているサービスに対して検索を依頼し、その結果を
132    画面下部のバッファに表示します。
133
134    modes.push()は、OUTPUT_MULTILINEモードから抜けたときに、AUTO_WORD_SELECT
135    モードに復帰させるために行っています。
136
137    追加されたモードに対するマッピングはプラグインを読み込んだ後に行う必要
138    があるので、registerObserver()で"enter"のタイミングでaddUserMap()している。
139
140    multi_requester.jsの使い方については、ソースのコメントや以下の
141    サイトなどを参照してください。
142    - http://vimperator.kurinton.net/plugins/multi_requester.html
143    - http://d.zeromemory.info/2008/11/20/vimperator-multi_requester.html
144
145  ]]></detail>
146</VimperatorPlugin>;
147// }}}
148
149(function(){
150
151const NEW_MODE = "AUTO_WORD_SELECT";
152const KEY = liberator.globalVariables.auto_word_select_key || 'I';
153
154if (!modes.AUTO_WORD_SELECT)
155  modes.addMode(NEW_MODE, false, function() NEW_MODE);
156
157// MAPPINGS {{{
158mappings.addUserMap(
159  [modes.NORMAL, modes.CARET, modes.VISUAL],
160  [KEY],
161  "Change to AUTO_WORD_SELECT mode.",
162  function() {
163    modes.push(modes.AUTO_WORD_SELECT);
164
165    if (content.getSelection().rangeCount == 0) {
166      let firstNode = content.document.body.firstChild;
167      let range = content.document.createRange();
168      range.setStart(firstNode, 0);
169      range.setEnd(firstNode, 0);
170      content.getSelection().addRange(range);
171    }
172  }
173);
174
175mappings.addUserMap(
176  [modes.AUTO_WORD_SELECT],
177  [KEY, "<Esc>"],
178  "Exit AUTO_WORD_SELECT mode.",
179  function() {
180    modes.pop();
181  }
182);
183
184mappings.add(
185  [modes.AUTO_WORD_SELECT],
186  [":"],
187  "Change command line mode.",
188  function() {
189    // FIXME:
190    // A present mode is preserved in the stack beforehand by the push() method
191    // because it doesn't return to AUTO_WORD_SELECT mode before that when
192    // exit from the COMMAND_LINE mode.
193    modes.push(modes.AUTO_WORD_SELECT, null, true);
194    mappings.get(modes.NORMAL, ":").action();
195  }
196);
197
198mappings.add(
199  [modes.AUTO_WORD_SELECT],
200  ["v"],
201  "Change visual mode.",
202  function() {
203    // FIXME:
204    // cannot return to modes.AUTO_WORD_SELECT when <Esc><Esc>
205    mappings.get(modes.NORMAL, "i").action();
206    mappings.get(modes.CARET, "v").action();
207  }
208);
209
210mappings.add(
211  [modes.AUTO_WORD_SELECT],
212  ["l"],
213  "Move to right word and select.",
214  function() {
215    controller().wordMove(true, false);
216    if (selectable()) selectWord();
217  }
218);
219
220mappings.add(
221  [modes.AUTO_WORD_SELECT],
222  ["L"],
223  "Extend to right word.",
224  function() {
225    var before = currentRange();
226    content.getSelection().collapseToEnd();
227    controller().wordMove(true, true);
228    currentRange().setStart(before.startContainer, before.startOffset);
229  }
230);
231
232mappings.add(
233  [modes.AUTO_WORD_SELECT],
234  ["h"],
235  "Move to left word and select.",
236  function() {
237    var before = currentRange();
238    content.getSelection().collapseToStart();
239    controller().wordMove(false, false);
240    if (selectable()) selectWord();
241
242    // FIXME:
243    // Because the caret doesn't move in a certain situation,
244    // the following ugly codes are added.
245    var after = currentRange();
246    if (compareRange(before, after)) {
247      content.getSelection().collapseToStart();
248      controller().wordMove(false, false);
249    }
250  }
251);
252
253mappings.add(
254  [modes.AUTO_WORD_SELECT],
255  ["H"],
256  "Extend to left word.",
257  function() {
258    var before = currentRange();
259    content.getSelection().collapseToStart();
260    controller().wordMove(false, true);
261    currentRange().setEnd(before.endContainer, before.endOffset);
262  }
263);
264
265mappings.add(
266  [modes.AUTO_WORD_SELECT],
267  ["j"],
268  "Move to below word and select.",
269  function() {
270    content.getSelection().collapseToStart();
271    controller().lineMove(true, false);
272    if (selectable()) selectWord();
273  }
274);
275
276mappings.add(
277  [modes.AUTO_WORD_SELECT],
278  ["k"],
279  "Move to above word and select.",
280  function() {
281    var before = currentRange();
282    content.getSelection().collapseToStart();
283    controller().lineMove(false, false);
284    if (selectable()) selectWord();
285
286    // FIXME:
287    // Because the caret doesn't move in a certain situation,
288    // the following ugly codes are added.
289    var after = currentRange();
290    if (compareRange(before, after)) {
291      content.getSelection().collapseToStart();
292      controller().lineMove(false, false);
293    }
294  }
295);
296
297// inherites key mappings from CARET mode
298[
299  // keys                     hasCount  caretModeMethod  caretModeArg
300  [["b", "B", "<C-Left>"],       true,  "wordMove",       false],
301  [["w", "W", "e", "<C-Right>"], true,  "wordMove",       true ],
302  [["<C-f>", "<PageDown>"],      true,  "pageMove",       true ],
303  [["<C-b>", "<PageUp>"],        true,  "pageMove",       false],
304  [["gg", "<C-Home>"],           false, "completeMove",   false],
305  [["G", "<C-End>"],             false, "completeMove",   true ],
306  [["0", "^", "<Home>"],         false, "intraLineMove",  false],
307  [["$", "<End>"],               false, "intraLineMove",  true ],
308].map(function(params) {
309  let [keys, hasCount, caretModeMethod, caretModeArg] = params;
310
311  let extraInfo = {};
312  if (hasCount) {
313    if (Mappings.flags)
314      extraInfo.flags = Mappings.flags.COUNT; // for backward compatibility
315    else
316      extraInfo.count = true;
317  }
318
319  mappings.add([modes.AUTO_WORD_SELECT], keys, "",
320    function (count) {
321      if (typeof count != "number" || count < 1)
322        count = 1;
323
324      let controller = buffer.selectionController;
325      while (count--)
326        controller[caretModeMethod](caretModeArg, false);
327
328      if (selectable()) selectWord();
329    },
330    extraInfo
331  );
332});
333
334// }}}
335// PRIVATE FUNCTIONS {{{
336function selectWord() {
337  controller().wordMove(true, false);
338  controller().wordMove(false, true);
339}
340
341function controller()
342  buffer.selectionController;
343
344function compareRange(a, b) {
345  return (a.startContainer.isSameNode(b.startContainer) &&
346          a.endContainer.isSameNode(b.endNode) &&
347          a.startOffset == b.startOffset &&
348          a.endOffset   == b.endOffset )
349            ? true
350            : false;
351}
352
353function currentRange()
354  content.getSelection().getRangeAt(0);
355
356function selectable() {
357  var sel = content.getSelection();
358  if (sel.anchorNode.nodeType != 3)
359    return false;
360  if (sel.anchorOffset == sel.anchorNode.textContent.length)
361    return false;
362
363  return true;
364}
365// }}}
366
367})();
368
369// vim:sw=2 ts=2 et si fdm=marker:
Note: See TracBrowser for help on using the browser.