aboutsummaryrefslogtreecommitdiffstats
path: root/auto_word_select_mode.js
blob: e32adea26191515150ce8194f754adc83ff7f346 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/*
 * ==VimperatorPlugin==
 * @name            mouseinspect.js
 * @description     display informations of the specified element and highlight it by mouse.
 * @description-ja  マウスで指定した要素の情報をコマンドラインに表示&ハイライトする。
 * @author          janus_wel <janus_wel@fb3.so-net.ne.jp>
 * @version         0.21
 * @minversion      2.0pre 2008/10/16
 * ==/VimperatorPlugin==
 *
 * LICENSE
 *   New BSD License
 *
 * CONSTRAINT
 *  need highlight.js
 *
 * USAGE
 *  :mouseinspect
 *  :mins
 *      -> start inspect by mouse
 *
 *  :nomouseinspect
 *  :nomins
 *      -> stop inspect and clear highlight
 *
 * SETTING
 *  blink_element_color:    color. default is red.
 *  blink_element_opacity:  opacity value. default is 0.5 .
 *
 * EXAMPLE
 *  let blink_element_color='green'
 *  let blink_element_opacity='0.7'
 * */

// use setTimeout to synchronize ( wait to process highlight.js )
// "liberator.modules.plugins.highlighterFactory" is build by highlight.js .
// it is the factory that build highlight object.
setTimeout( function () {

if (!plugins.highlighterFactory) {
    liberator.log('mouseinspect.js needs highlight.js', 0);
    return;
}

// default settings
const defaultColor   = 'red';
const defaultOpacity = 0.5;

// main
let elementInfo = function (event) {
    let element = event.target;

    if (element.className === 'vimp_plugin_highlightelement') {
        elementInfo.highlighter.unhighlightAll();
        return;
    }

    let attributes = [a.name + '="' + a.value + '"' for (a in util.Array.iterator(element.attributes))].join(' ');
    let str = '<' + element.localName.toLowerCase() + (attributes ? ' ' + attributes : '') + '>';
    liberator.echo(str, commandline.FORCE_SINGLELINE);

    elementInfo.highlighter.highlight(element);
};

let setupHighlighter = function () {
    elementInfo.highlighter = plugins.highlighterFactory({
        color:    liberator.globalVariables.mouse_inspect_color   || defaultColor,
        opacity:  liberator.globalVariables.mouse_inspect_opacity || defaultOpacity,
        interval: 0,
    });
}

setupHighlighter();

// register commands
commands.addUserCommand(
    ['mouseinspect', 'mins'],
    'mouse',
    function () {
        setupHighlighter();
        window.addEventListener('mousemove', elementInfo, false);
    },
    {}
);
commands.addUserCommand(
    ['nomouseinspect', 'nomins'],
    'mouse',
    function () {
        window.removeEventListener('mousemove', elementInfo, false);
   
//
// auto_word_select_mode.js
//
// LICENSE: {{{
//
//     This software distributable under the terms of an MIT-style license.
//
//     Copyright (c) 2009 snaka<snaka.gml@gmail.com>
//
//     Permission is hereby granted, free of charge, to any person obtaining a copy
//     of this software and associated documentation files (the "Software"), to deal
//     in the Software without restriction, including without limitation the rights
//     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//     copies of the Software, and to permit persons to whom the Software is
//     furnished to do so, subject to the following conditions:
//
//     The above copyright notice and this permission notice shall be included in
//     all copies or substantial portions of the Software.
//
//     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//     THE SOFTWARE.
//
//     OSI page : http://opensource.org/licenses/mit-license.php
//     Japanese : http://sourceforge.jp/projects/opensource/wiki/licenses%2FMIT_license
//
// }}}

// PLUGIN INFO: {{{
var PLUGIN_INFO =
<VimperatorPlugin>
  <name>{NAME}</name>
  <description>Add auto word select mode.</description>
  <description lang="ja">単語を自動選択するモードを追加します</description>
  <minVersion>2.0</minVersion>
  <maxVersion>2.2pre</maxVersion>
  <updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/auto_word_select_mode.js</updateURL>
  <author mail="snaka.gml@gmail.com" homepage="http://vimperator.g.hatena.ne.jp/snaka72/">snaka</author>
  <license>MIT style license</license>
  <version>1.2.3</version>
  <detail><![CDATA[
    == Subject ==
    Add auto word select mode.
    Mode name is "AUTO_WORD_SELECT".
    Press 'I' key to entering to AUTO_WORD_SELECT mode.
    If you want exit mode, press 'I' key again.
    This mode alway selects current word.

    == Global variables ==
    g:auto_word_select_key:
      The key that entering to AUTO_WORD_SELECT mode.
      Default is 'I'.

    == About define keymap with AUTO_WORD_SELECT mode ==
    The example of defining the key-map for the AUTO_WORD_SELECT mode
    is shown as follows.

    The following definitions are examples of assign behavior that
    displays the translation result of the word selecting it by 'alc'
    service by using multi_requester.js for 's' key.

    >||
    liberator.registerObserver("enter", function() {
      mappings.addUserMap(
        [modes.AUTO_WORD_SELECT],
        ["s"],
        "Translate selected word by multi_requester.js.",
        function() {
          // FIXME:
          // A present mode is preserved in the stack beforehand by the push() method
          // because it doesn't return to AUTO_WORD_SELECT mode before that when
          // returning from the OUTPUT_MULTILINE mode.
          modes.push(modes.AUTO_WORD_SELECT, null, true);

          var selText = content.getSelection().toString();
          var pattern = /[a-zA-Z]+/;
          selText = pattern.test(selText) ? pattern.exec(selText) : selText;
          events.feedkeys(":mr alc " + selText + "<CR>", true, true);
        }
      );
    });
    ||<

  ]]></detail>
  <detail lang="ja"><![CDATA[
    == 概要 ==
    単語を自動選択するモード(AUTO_WORD_SELECT)を追加します
    'I'キーを押すことによってAUTO_WORD_SELECTモードに移行します
    このモードを抜けるには再度'I'キーを押します
    このモードでは常に単語が選択されている状態になりキャレットの
    移動の単位も単語毎の移動になります
    コンテンツ内の単語を頻繁に選択検索する場合などに便利です

    == グローバル変数 ==
    g:auto_word_select_key:
      AUTO_WORD_SELECTモードに移行するためのキーです
      デフォルトは'I'です

    == AUTO_WORD_SELECTモード用のマップの定義について ==
    このモード用のマップの定義例としてmulti_requester.jsを使用して
    web上の辞書サービスを使用して検索結果を表示するためのマップを
    定義する例を示します

    >||
    liberator.registerObserver("enter", function() {
      mappings.addUserMap(
        [modes.AUTO_WORD_SELECT],
        ["s"],
        "Translate selected word by multi_requester.js.",
        function() {
          // FIXME:
          // A present mode is preserved in the stack beforehand by the push() method
          // because it doesn't return to AUTO_WORD_SELECT mode before that when
          // returning from the OUTPUT_MULTILINE mode.
          modes.push(modes.AUTO_WORD_SELECT, null, true);

          var selText = content.getSelection().toString();
          var pattern = /[a-zA-Z]+/;
          selText = pattern.test(selText) ? pattern.exec(selText) : selText;
          events.feedkeys(":mr alc " + selText + "<CR>", true, true);
        }
      );
    });
    ||<

    上記の例ではAUTO_WORD_SELECTモードにおいて単語を選択した後's'キーを
    押すと'alc'で登録されているサービスに対して検索を依頼しその結果を
    画面下部のバッファに表示します

    modes.push()OUTPUT_MULTILINEモードから抜けたときにAUTO_WORD_SELECT
    モードに復帰させるために行っています

    追加されたモードに対するマッピングはプラグインを読み込んだ後に行う必要
    があるのでregisterObserver()"enter"のタイミングでaddUserMap()している

    multi_requester.jsの使い方についてはソースのコメントや以下の
    サイトなどを参照してください
    - http://vimperator.kurinton.net/plugins/multi_requester.html
    - http://d.zeromemory.info/2008/11/20/vimperator-multi_requester.html

  ]]></detail>
</VimperatorPlugin>;
// }}}

(function(){

const NEW_MODE = "AUTO_WORD_SELECT";
const KEY = liberator.globalVariables.auto_word_select_key || 'I';

if (!modes.AUTO_WORD_SELECT)
  modes.addMode(NEW_MODE, false, function() NEW_MODE);

// MAPPINGS {{{
mappings.addUserMap(
  [modes.NORMAL, modes.CARET, modes.VISUAL],
  [KEY],
  "Change to AUTO_WORD_SELECT mode.",
  function() {
    modes.push(modes.AUTO_WORD_SELECT);

    if (content.getSelection().rangeCount == 0) {
      let firstNode = content.document.body.firstChild;
      let range = content.document.createRange();
      range.setStart(firstNode, 0);
      range.setEnd(firstNode, 0);
      content.getSelection().addRange(range);
    }
  }
);

mappings.addUserMap(
  [modes.AUTO_WORD_SELECT],
  [KEY, "<Esc>"],
  "Exit AUTO_WORD_SELECT mode.",
  function() {
    modes.pop();
  }
);

mappings.add(
  [modes.AUTO_WORD_SELECT],
  [":"],
  "Change command line mode.",
  function() {
    // FIXME:
    // A present mode is preserved in the stack beforehand by the push() method
    // because it doesn't return to AUTO_WORD_SELECT mode before that when
    // exit from the COMMAND_LINE mode.
    modes.push(modes.AUTO_WORD_SELECT, null, true);
    mappings.get(modes.NORMAL, ":").action();
  }
);

mappings.add(
  [modes.AUTO_WORD_SELECT],
  ["v"],
  "Change visual mode.",
  function() {
    // FIXME:
    // cannot return to modes.AUTO_WORD_SELECT when <Esc><Esc>
    mappings.get(modes.NORMAL, "i").action();
    mappings.get(modes.CARET, "v").action();
  }
);

mappings.add(
  [modes.AUTO_WORD_SELECT],
  ["l"],
  "Move to right word and select.",
  function() {
    controller().wordMove(true, false);
    if (selectable()) selectWord();
  }
);

mappings.add(
  [modes.AUTO_WORD_SELECT],
  ["L"],
  "Extend to right word.",
  function() {
    var before = currentRange();
    content.getSelection().collapseToEnd();
    controller().wordMove(true, true);
    currentRange().setStart(before.startContainer, before.startOffset);
  }
);

mappings.add(
  [modes.AUTO_WORD_SELECT],
  ["h"],
  "Move to left word and select.",
  function() {
    var before = currentRange();
    content.getSelection().collapseToStart();
    controller().wordMove(false, false);
    if (selectable()) selectWord();

    // FIXME:
    // Because the caret doesn't move in a certain situation,
    // the following ugly codes are added.
    var after = currentRange();
    if (compareRange(before, after)) {
      content.getSelection().collapseToStart();
      controller().wordMove(false, false);
    }
  }
);

mappings.add(
  [modes.AUTO_WORD_SELECT],
  ["H"],
  "Extend to left word.",
  function() {
    var before = currentRange();
    content.getSelection().collapseToStart();
    controller().wordMove(false, true);
    currentRange().setEnd(before.endContainer, before.endOffset);
  }
);

mappings.add(
  [modes.AUTO_WORD_SELECT],
  ["j"],
  "Move to below word and select.",
  function() {
    content.getSelection().collapseToStart();
    controller().lineMove(true, false);
    if (selectable()) selectWord();
  }
);

mappings.add(
  [modes.AUTO_WORD_SELECT],
  ["k"],
  "Move to above word and select.",
  function() {
    var before = currentRange();
    content.getSelection().collapseToStart();
    controller().lineMove(false, false);
    if (selectable()) selectWord();

    // FIXME:
    // Because the caret doesn't move in a certain situation,
    // the following ugly codes are added.
    var after = currentRange();
    if (compareRange(before, after)) {
      content.getSelection().collapseToStart();
      controller().lineMove(false, false);
    }
  }
);

// inherites key mappings from CARET mode
[
  // keys                     hasCount  caretModeMethod  caretModeArg
  [["b", "B", "<C-Left>"],       true,  "wordMove",       false],
  [["w", "W", "e", "<C-Right>"], true,  "wordMove",       true ],
  [["<C-f>", "<PageDown>"],      true,  "pageMove",       true ],
  [["<C-b>", "<PageUp>"],        true,  "pageMove",       false],
  [["gg", "<C-Home>"],           false, "completeMove",   false],
  [["G", "<C-End>"],             false, "completeMove",   true ],
  [["0", "^", "<Home>"],         false, "intraLineMove",  false],
  [["$", "<End>"],               false, "intraLineMove",  true ],
].map(function(params) {
  let [keys, hasCount, caretModeMethod, caretModeArg] = params;

  let extraInfo = {};
  if (hasCount) {
    if (Mappings.flags)
      extraInfo.flags = Mappings.flags.COUNT; // for backward compatibility
    else
      extraInfo.count = true;
  }

  mappings.add([modes.AUTO_WORD_SELECT], keys, "",
    function (count) {
      if (typeof count != "number" || count < 1)
        count = 1;

      let controller = buffer.selectionController;
      while (count--)
        controller[caretModeMethod](caretModeArg, false);

      if (selectable()) selectWord();
    },
    extraInfo
  );
});

// }}}
// PRIVATE FUNCTIONS {{{
function selectWord() {
  controller().wordMove(true, false);
  controller().wordMove(false, true);
}

function controller()
  buffer.selectionController;

function compareRange(a, b) {
  return (a.startContainer.isSameNode(b.startContainer) &&
          a.endContainer.isSameNode(b.endNode) &&
          a.startOffset == b.startOffset &&
          a.endOffset   == b.endOffset )
            ? true
            : false;
}

function currentRange()
  content.getSelection().getRangeAt(0);

function selectable() {
  var sel = content.getSelection();
  if (sel.anchorNode.nodeType != 3)
    return false;
  if (sel.anchorOffset == sel.anchorNode.textContent.length)
    return false;

  return true;
}
// }}}

})();

// vim:sw=2 ts=2 et si fdm=marker: