| 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: {{{ |
|---|
| 34 | var 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 | |
|---|
| 151 | const NEW_MODE = "AUTO_WORD_SELECT"; |
|---|
| 152 | const KEY = liberator.globalVariables.auto_word_select_key || 'I'; |
|---|
| 153 | |
|---|
| 154 | if (!modes.AUTO_WORD_SELECT) |
|---|
| 155 | modes.addMode(NEW_MODE, false, function() NEW_MODE); |
|---|
| 156 | |
|---|
| 157 | // MAPPINGS {{{ |
|---|
| 158 | mappings.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 | |
|---|
| 175 | mappings.addUserMap( |
|---|
| 176 | [modes.AUTO_WORD_SELECT], |
|---|
| 177 | [KEY, "<Esc>"], |
|---|
| 178 | "Exit AUTO_WORD_SELECT mode.", |
|---|
| 179 | function() { |
|---|
| 180 | modes.pop(); |
|---|
| 181 | } |
|---|
| 182 | ); |
|---|
| 183 | |
|---|
| 184 | mappings.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 | |
|---|
| 198 | mappings.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 | |
|---|
| 210 | mappings.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 | |
|---|
| 220 | mappings.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 | |
|---|
| 232 | mappings.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 | |
|---|
| 253 | mappings.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 | |
|---|
| 265 | mappings.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 | |
|---|
| 276 | mappings.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 {{{ |
|---|
| 330 | function selectWord() { |
|---|
| 331 | controller().wordMove(true, false); |
|---|
| 332 | controller().wordMove(false, true); |
|---|
| 333 | } |
|---|
| 334 | |
|---|
| 335 | function controller() |
|---|
| 336 | buffer.selectionController; |
|---|
| 337 | |
|---|
| 338 | function 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 | |
|---|
| 347 | function currentRange() |
|---|
| 348 | content.getSelection().getRangeAt(0); |
|---|
| 349 | |
|---|
| 350 | function 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 | |
|---|
| 370 | liberator.echo("loading ..."); |
|---|
| 371 | })(); |
|---|
| 372 | |
|---|
| 373 | // vim:sw=2 ts=2 et si fdm=marker: |
|---|