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

Revision 33539, 12.0 kB (checked in by snaka, 16 months ago)

CARETモードの標準のキーバインドと同じものを追加

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.0pre</minVersion>
40  <maxVersion>2.1pre</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.1</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      extraInfo.flags = Mappings.flags.COUNT;
314
315  mappings.add([modes.AUTO_WORD_SELECT], keys, "",
316    function (count) {
317      if (typeof count != "number" || count < 1)
318        count = 1;
319
320      let controller = buffer.selectionController;
321      while (count--)
322        controller[caretModeMethod](caretModeArg, false);
323    },
324    extraInfo
325  );
326});
327
328// }}}
329// PRIVATE FUNCTIONS {{{
330function selectWord() {
331  controller().wordMove(true, false);
332  controller().wordMove(false, true);
333}
334
335function controller()
336  buffer.selectionController;
337
338function compareRange(a, b) {
339  return (a.startContainer.isSameNode(b.startContainer) &&
340          a.endContainer.isSameNode(b.endNode) &&
341          a.startOffset == b.startOffset &&
342          a.endOffset   == b.endOffset )
343            ? true
344            : false;
345}
346
347function currentRange()
348  content.getSelection().getRangeAt(0);
349
350function selectable() {
351  var sel = content.getSelection();
352  if (sel.anchorNode.nodeType != 3)
353    return false;
354  if (sel.anchorOffset == sel.anchorNode.textContent.length)
355    return false;
356
357  return true;
358}
359// }}}
360
361//// for debuging
362//liberator.registerObserver("modeChange", function(oldModes, newModes, stack) {
363//  liberator.dump(getModeName(oldModes[0]) +" + "+ getModeName(oldModes[1])
364//                 + " -> " +
365//                 getModeName(newModes[0]) +" + "+ getModeName(newModes[1]));
366//  liberator.dumpStack();
367//});
368//function getModeName(id) modes.getMode(id) ? modes.getMode(id).name : "";
369
370liberator.echo("loading ...");
371})();
372
373// vim:sw=2 ts=2 et si fdm=marker:
Note: See TracBrowser for help on using the browser.